From 947f5f346c7eb4412c74491a8a97de4ae3b2d22b Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:19:19 +0300 Subject: [PATCH 001/111] RUM-5540: Add stop and start apis for session replay --- .../internal/TelemetryEventHandler.kt | 10 ++--- .../TelemetryConfigurationEventAssert.kt | 9 ++-- .../internal/TelemetryEventHandlerTest.kt | 18 ++++---- .../api/apiSurface | 4 +- .../api/dd-sdk-android-session-replay.api | 7 +++- .../android/sessionreplay/SessionReplay.kt | 41 ++++++++++++++++++- .../internal/SessionReplayFeature.kt | 18 ++++---- .../sessionreplay/SessionReplayTest.kt | 38 +++++++++++++++++ .../internal/SessionReplayFeatureTest.kt | 10 +++-- 9 files changed, 122 insertions(+), 33 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 92856f6466..3add64ce4f 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -250,8 +250,8 @@ internal class TelemetryEventHandler( sdkCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME) val sessionReplaySampleRate = sessionReplayFeatureContext[SESSION_REPLAY_SAMPLE_RATE_KEY] as? Long - val startSessionReplayManually = - sessionReplayFeatureContext[SESSION_REPLAY_MANUAL_RECORDING_KEY] as? Boolean + val startSessionReplayImmediately = + sessionReplayFeatureContext[SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY] as? Boolean val sessionReplayPrivacy = sessionReplayFeatureContext[SESSION_REPLAY_PRIVACY_KEY] as? String val rumConfig = sdkCore.getFeature(Feature.RUM_FEATURE_NAME) @@ -315,7 +315,7 @@ internal class TelemetryEventHandler( trackNetworkRequests = trackNetworkRequests, sessionReplaySampleRate = sessionReplaySampleRate, defaultPrivacyLevel = sessionReplayPrivacy, - startSessionReplayRecordingManually = startSessionReplayManually, + startRecordingImmediately = startSessionReplayImmediately, batchProcessingLevel = coreConfiguration.batchProcessingLevel.toLong() ) ) @@ -394,7 +394,7 @@ internal class TelemetryEventHandler( internal const val OPENTELEMETRY_API_VERSION_CONTEXT_KEY = "opentelemetry_api_version" internal const val SESSION_REPLAY_SAMPLE_RATE_KEY = "session_replay_sample_rate" internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" - internal const val SESSION_REPLAY_MANUAL_RECORDING_KEY = - "session_replay_requires_manual_recording" + internal const val SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY = + "session_replay_start_immediate_recording" } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt index 1c65d43645..e952f8f987 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt @@ -269,6 +269,7 @@ internal class TelemetryConfigurationEventAssert(actual: TelemetryConfigurationE .isEqualTo(expected) return this } + fun hasBatchProcessingLevel(expected: Int?): TelemetryConfigurationEventAssert { assertThat(actual.telemetry.configuration.batchProcessingLevel) .overridingErrorMessage( @@ -359,12 +360,12 @@ internal class TelemetryConfigurationEventAssert(actual: TelemetryConfigurationE return this } - fun hasSessionReplayStartManually(expected: Boolean?): TelemetryConfigurationEventAssert { + fun hasStartRecordingImmediately(expected: Boolean?): TelemetryConfigurationEventAssert { val assertErrorMessage = "Expected event data to have" + - " telemetry.configuration.startSessionReplayRecordingManually" + + " telemetry.configuration.startRecordingImmediately" + " $expected " + - "but was ${actual.telemetry.configuration.startSessionReplayRecordingManually}" - assertThat(actual.telemetry.configuration.startSessionReplayRecordingManually) + "but was ${actual.telemetry.configuration.startRecordingImmediately}" + assertThat(actual.telemetry.configuration.startRecordingImmediately) .overridingErrorMessage(assertErrorMessage) .isEqualTo(expected) return this diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index fc78624e26..907ba04c2f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -478,7 +478,7 @@ internal class TelemetryEventHandlerTest { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) assertConfigEventMatchesRawEvent(firstValue, configRawEvent) assertThat(firstValue).hasSessionReplaySampleRate(null) - assertThat(firstValue).hasSessionReplayStartManually(null) + assertThat(firstValue).hasStartRecordingImmediately(null) assertThat(firstValue).hasSessionReplayPrivacy(null) } } @@ -490,11 +490,11 @@ internal class TelemetryEventHandlerTest { // Given val fakeSampleRate = forge.aPositiveLong() val fakeSessionReplayPrivacy = forge.aString() - val fakeSessionReplayIsStartManually = forge.aBool() + val fakeSessionReplayIsStartImmediately = forge.aBool() val fakeSessionReplayContext = mutableMapOf( TelemetryEventHandler.SESSION_REPLAY_PRIVACY_KEY to fakeSessionReplayPrivacy, - TelemetryEventHandler.SESSION_REPLAY_MANUAL_RECORDING_KEY to - fakeSessionReplayIsStartManually, + TelemetryEventHandler.SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY to + fakeSessionReplayIsStartImmediately, TelemetryEventHandler.SESSION_REPLAY_SAMPLE_RATE_KEY to fakeSampleRate ) whenever(mockSdkCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn @@ -509,7 +509,7 @@ internal class TelemetryEventHandlerTest { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) assertConfigEventMatchesRawEvent(firstValue, configRawEvent) assertThat(firstValue).hasSessionReplaySampleRate(fakeSampleRate) - assertThat(firstValue).hasSessionReplayStartManually(fakeSessionReplayIsStartManually) + assertThat(firstValue).hasStartRecordingImmediately(fakeSessionReplayIsStartImmediately) assertThat(firstValue).hasSessionReplayPrivacy(fakeSessionReplayPrivacy) } } @@ -521,11 +521,11 @@ internal class TelemetryEventHandlerTest { // Given val fakeSampleRate = forge.aNullable { aString() } val fakeSessionReplayPrivacy = forge.aNullable { aLong() } - val fakeSessionReplayIsStartManually = forge.aNullable { aString() } + val fakeSessionReplayIsStartedImmediatley = forge.aNullable { aString() } val fakeSessionReplayContext = mutableMapOf( TelemetryEventHandler.SESSION_REPLAY_PRIVACY_KEY to fakeSessionReplayPrivacy, - TelemetryEventHandler.SESSION_REPLAY_MANUAL_RECORDING_KEY to - fakeSessionReplayIsStartManually, + TelemetryEventHandler.SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY to + fakeSessionReplayIsStartedImmediatley, TelemetryEventHandler.SESSION_REPLAY_SAMPLE_RATE_KEY to fakeSampleRate ) whenever(mockSdkCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn @@ -540,7 +540,7 @@ internal class TelemetryEventHandlerTest { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) assertConfigEventMatchesRawEvent(firstValue, configRawEvent) assertThat(firstValue).hasSessionReplaySampleRate(null) - assertThat(firstValue).hasSessionReplayStartManually(null) + assertThat(firstValue).hasStartRecordingImmediately(null) assertThat(firstValue).hasSessionReplayPrivacy(null) } } diff --git a/features/dd-sdk-android-session-replay/api/apiSurface b/features/dd-sdk-android-session-replay/api/apiSurface index c0af4ac698..cb020e9ec7 100644 --- a/features/dd-sdk-android-session-replay/api/apiSurface +++ b/features/dd-sdk-android-session-replay/api/apiSurface @@ -10,7 +10,9 @@ data class com.datadog.android.sessionreplay.MapperTypeWrapper object com.datadog.android.sessionreplay.SessionReplay - fun enable(SessionReplayConfiguration, com.datadog.android.api.SdkCore = Datadog.getInstance()) + fun enable(SessionReplayConfiguration, com.datadog.android.api.SdkCore = Datadog.getInstance(), Boolean = true) + fun startRecording(com.datadog.android.api.SdkCore = Datadog.getInstance()) + fun stopRecording(com.datadog.android.api.SdkCore = Datadog.getInstance()) data class com.datadog.android.sessionreplay.SessionReplayConfiguration class Builder constructor(Float) diff --git a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api index b2060b910e..65349d68e8 100644 --- a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api +++ b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api @@ -26,7 +26,12 @@ public final class com/datadog/android/sessionreplay/SessionReplay { public static final field INSTANCE Lcom/datadog/android/sessionreplay/SessionReplay; public static final fun enable (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;)V public static final fun enable (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;)V - public static synthetic fun enable$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V + public static final fun enable (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;Z)V + public static synthetic fun enable$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;ZILjava/lang/Object;)V + public final fun startRecording (Lcom/datadog/android/api/SdkCore;)V + public static synthetic fun startRecording$default (Lcom/datadog/android/sessionreplay/SessionReplay;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V + public final fun stopRecording (Lcom/datadog/android/api/SdkCore;)V + public static synthetic fun stopRecording$default (Lcom/datadog/android/sessionreplay/SessionReplay;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V } public final class com/datadog/android/sessionreplay/SessionReplayConfiguration { diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt index ba1492a830..9bb7756e70 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt @@ -8,6 +8,7 @@ package com.datadog.android.sessionreplay import com.datadog.android.Datadog import com.datadog.android.api.SdkCore +import com.datadog.android.api.feature.Feature.Companion.SESSION_REPLAY_FEATURE_NAME import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.internal.SessionReplayFeature @@ -22,12 +23,15 @@ object SessionReplay { * @param sessionReplayConfiguration Configuration to use for the feature. * @param sdkCore SDK instance to register feature in. If not provided, default SDK instance * will be used. + * @param startRecordingImmediately whether to start recording immediately or wait for manual start. + * If not provided, the default is to start immediately. */ @JvmOverloads @JvmStatic fun enable( sessionReplayConfiguration: SessionReplayConfiguration, - sdkCore: SdkCore = Datadog.getInstance() + sdkCore: SdkCore = Datadog.getInstance(), + startRecordingImmediately: Boolean = true ) { val sessionReplayFeature = SessionReplayFeature( sdkCore = sdkCore as FeatureSdkCore, @@ -36,9 +40,42 @@ object SessionReplay { imagePrivacy = sessionReplayConfiguration.imagePrivacy, customMappers = sessionReplayConfiguration.customMappers, customOptionSelectorDetectors = sessionReplayConfiguration.customOptionSelectorDetectors, - sampleRate = sessionReplayConfiguration.sampleRate + sampleRate = sessionReplayConfiguration.sampleRate, + startRecordingImmediately = startRecordingImmediately ) sdkCore.registerFeature(sessionReplayFeature) } + + /** + * Start recording session replay data. + * @param sdkCore SDK instance to get the feature from. If not provided, default SDK instance + * will be used. + */ + fun startRecording( + sdkCore: SdkCore = Datadog.getInstance() + ) { + val sessionReplayFeature = (sdkCore as? FeatureSdkCore) + ?.getFeature(SESSION_REPLAY_FEATURE_NAME)?.let { + it.unwrap() as? SessionReplayFeature + } + + sessionReplayFeature?.startRecording() + } + + /** + * Stop recording session replay data. + * @param sdkCore SDK instance to get the feature from. If not provided, default SDK instance + * will be used. + */ + fun stopRecording( + sdkCore: SdkCore = Datadog.getInstance() + ) { + val sessionReplayFeature = (sdkCore as? FeatureSdkCore) + ?.getFeature(SESSION_REPLAY_FEATURE_NAME)?.let { + it.unwrap() as? SessionReplayFeature + } + + sessionReplayFeature?.stopRecording() + } } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index f3c3f0e225..2a94054c73 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -45,6 +45,7 @@ internal class SessionReplayFeature( internal val privacy: SessionReplayPrivacy, internal val imagePrivacy: ImagePrivacy?, private val rateBasedSampler: Sampler, + private val startRecordingImmediately: Boolean, private val recorderProvider: RecorderProvider ) : StorageBackedFeature, FeatureEventReceiver { @@ -57,13 +58,15 @@ internal class SessionReplayFeature( imagePrivacy: ImagePrivacy, customMappers: List>, customOptionSelectorDetectors: List, - sampleRate: Float + sampleRate: Float, + startRecordingImmediately: Boolean ) : this( sdkCore, customEndpointUrl, privacy, imagePrivacy, RateBasedSampler(sampleRate), + startRecordingImmediately, DefaultRecorderProvider( sdkCore, privacy, @@ -117,10 +120,7 @@ internal class SessionReplayFeature( sdkCore.updateFeatureContext(SESSION_REPLAY_FEATURE_NAME) { it[SESSION_REPLAY_SAMPLE_RATE_KEY] = rateBasedSampler.getSampleRate()?.toLong() it[SESSION_REPLAY_PRIVACY_KEY] = privacy.toString().lowercase(Locale.US) - // False by default. This will be changed once we will conform to the browser SR - // implementation where a parameter will be passed in the Configuration constructor - // to enable manual recording. - it[SESSION_REPLAY_MANUAL_RECORDING_KEY] = false + it[SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY] = startRecordingImmediately } } @@ -205,7 +205,9 @@ internal class SessionReplayFeature( return } - if (keepSession && rateBasedSampler.sample()) { + val shouldRecord = startRecordingImmediately || isRecording.get() + + if (shouldRecord && keepSession && rateBasedSampler.sample()) { startRecording() } else { sdkCore.internalLogger.log( @@ -312,8 +314,8 @@ internal class SessionReplayFeature( const val RUM_SESSION_ID_BUS_MESSAGE_KEY = "sessionId" internal const val SESSION_REPLAY_SAMPLE_RATE_KEY = "session_replay_sample_rate" internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" - internal const val SESSION_REPLAY_MANUAL_RECORDING_KEY = - "session_replay_requires_manual_recording" + internal const val SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY = + "session_replay_start_immediate_recording" internal const val SESSION_REPLAY_ENABLED_KEY = "session_replay_is_enabled" } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt index 3e9f333415..0c78a82185 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt @@ -6,6 +6,8 @@ package com.datadog.android.sessionreplay +import com.datadog.android.api.feature.Feature.Companion.SESSION_REPLAY_FEATURE_NAME +import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.SessionReplayFeature @@ -65,4 +67,40 @@ internal class SessionReplayTest { .isEqualTo(fakeSessionReplayConfiguration.customEndpointUrl) } } + + @Test + fun `M call startRecording on feature W startRecording`( + @Mock mockFeatureScope: FeatureScope, + @Mock mockSessionReplayFeature: SessionReplayFeature + ) { + // Given + whenever(mockSdkCore.getFeature(SESSION_REPLAY_FEATURE_NAME)) + .thenReturn(mockFeatureScope) + + whenever(mockFeatureScope.unwrap()) doReturn mockSessionReplayFeature + + // When + SessionReplay.startRecording(mockSdkCore) + + // Then + verify(mockSessionReplayFeature).startRecording() + } + + @Test + fun `M call stopRecording on feature W stopRecording`( + @Mock mockFeatureScope: FeatureScope, + @Mock mockSessionReplayFeature: SessionReplayFeature + ) { + // Given + whenever(mockSdkCore.getFeature(SESSION_REPLAY_FEATURE_NAME)) + .thenReturn(mockFeatureScope) + + whenever(mockFeatureScope.unwrap()) doReturn mockSessionReplayFeature + + // When + SessionReplay.stopRecording(mockSdkCore) + + // Then + verify(mockSessionReplayFeature).stopRecording() + } } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt index 546463bb09..a4cec933d8 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt @@ -100,6 +100,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + startRecordingImmediately = true, rateBasedSampler = mockSampler ) { _, _, _, _ -> mockRecorder } } @@ -124,6 +125,7 @@ internal class SessionReplayFeatureTest { imagePrivacy = fakeConfiguration.imagePrivacy, customMappers = emptyList(), customOptionSelectorDetectors = emptyList(), + startRecordingImmediately = true, sampleRate = fakeConfiguration.sampleRate ) @@ -145,7 +147,8 @@ internal class SessionReplayFeatureTest { imagePrivacy = fakeConfiguration.imagePrivacy, customMappers = emptyList(), customOptionSelectorDetectors = emptyList(), - sampleRate = fakeConfiguration.sampleRate + sampleRate = fakeConfiguration.sampleRate, + startRecordingImmediately = true ) // When @@ -163,8 +166,8 @@ internal class SessionReplayFeatureTest { .isEqualTo(fakeConfiguration.sampleRate.toLong()) assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_PRIVACY_KEY]) .isEqualTo(fakeConfiguration.privacy.toString().lowercase(Locale.US)) - assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_MANUAL_RECORDING_KEY]) - .isEqualTo(false) + assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY]) + .isEqualTo(true) } } @@ -176,6 +179,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + startRecordingImmediately = true, rateBasedSampler = mockSampler ) { _, _, _, _ -> mockRecorder } From 1cb4f452c16ba122292391290422068323108bde Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Sun, 11 Aug 2024 22:36:45 +0300 Subject: [PATCH 002/111] RUM-5540: Move startRecordingImmediately to config --- .../api/apiSurface | 3 +- .../api/dd-sdk-android-session-replay.api | 8 +-- .../android/sessionreplay/SessionReplay.kt | 11 ++-- .../SessionReplayConfiguration.kt | 17 +++++- .../internal/SessionReplayFeature.kt | 31 +++++++--- .../SessionReplayConfigurationBuilderTest.kt | 11 ++++ .../sessionreplay/SessionReplayTest.kt | 8 +-- ...essionReplayConfigurationForgeryFactory.kt | 1 + .../internal/SessionReplayFeatureTest.kt | 60 +++++++++++++++++++ 9 files changed, 125 insertions(+), 25 deletions(-) diff --git a/features/dd-sdk-android-session-replay/api/apiSurface b/features/dd-sdk-android-session-replay/api/apiSurface index cb020e9ec7..dae4fabe0f 100644 --- a/features/dd-sdk-android-session-replay/api/apiSurface +++ b/features/dd-sdk-android-session-replay/api/apiSurface @@ -10,7 +10,7 @@ data class com.datadog.android.sessionreplay.MapperTypeWrapper object com.datadog.android.sessionreplay.SessionReplay - fun enable(SessionReplayConfiguration, com.datadog.android.api.SdkCore = Datadog.getInstance(), Boolean = true) + fun enable(SessionReplayConfiguration, com.datadog.android.api.SdkCore = Datadog.getInstance()) fun startRecording(com.datadog.android.api.SdkCore = Datadog.getInstance()) fun stopRecording(com.datadog.android.api.SdkCore = Datadog.getInstance()) data class com.datadog.android.sessionreplay.SessionReplayConfiguration @@ -19,6 +19,7 @@ data class com.datadog.android.sessionreplay.SessionReplayConfiguration fun addExtensionSupport(ExtensionSupport): Builder fun useCustomEndpoint(String): Builder fun setPrivacy(SessionReplayPrivacy): Builder + fun startRecordingImmediately(Boolean): Builder fun build(): SessionReplayConfiguration enum com.datadog.android.sessionreplay.SessionReplayPrivacy - ALLOW diff --git a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api index 65349d68e8..1eb9552874 100644 --- a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api +++ b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api @@ -26,8 +26,7 @@ public final class com/datadog/android/sessionreplay/SessionReplay { public static final field INSTANCE Lcom/datadog/android/sessionreplay/SessionReplay; public static final fun enable (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;)V public static final fun enable (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;)V - public static final fun enable (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;Z)V - public static synthetic fun enable$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;ZILjava/lang/Object;)V + public static synthetic fun enable$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V public final fun startRecording (Lcom/datadog/android/api/SdkCore;)V public static synthetic fun startRecording$default (Lcom/datadog/android/sessionreplay/SessionReplay;Lcom/datadog/android/api/SdkCore;ILjava/lang/Object;)V public final fun stopRecording (Lcom/datadog/android/api/SdkCore;)V @@ -35,8 +34,8 @@ public final class com/datadog/android/sessionreplay/SessionReplay { } public final class com/datadog/android/sessionreplay/SessionReplayConfiguration { - public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; - public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -47,6 +46,7 @@ public final class com/datadog/android/sessionreplay/SessionReplayConfiguration$ public final fun addExtensionSupport (Lcom/datadog/android/sessionreplay/ExtensionSupport;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun build ()Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; public final fun setPrivacy (Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; + public final fun startRecordingImmediately (Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun useCustomEndpoint (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt index 9bb7756e70..fa48f432ef 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt @@ -23,15 +23,12 @@ object SessionReplay { * @param sessionReplayConfiguration Configuration to use for the feature. * @param sdkCore SDK instance to register feature in. If not provided, default SDK instance * will be used. - * @param startRecordingImmediately whether to start recording immediately or wait for manual start. - * If not provided, the default is to start immediately. */ @JvmOverloads @JvmStatic fun enable( sessionReplayConfiguration: SessionReplayConfiguration, - sdkCore: SdkCore = Datadog.getInstance(), - startRecordingImmediately: Boolean = true + sdkCore: SdkCore = Datadog.getInstance() ) { val sessionReplayFeature = SessionReplayFeature( sdkCore = sdkCore as FeatureSdkCore, @@ -41,7 +38,7 @@ object SessionReplay { customMappers = sessionReplayConfiguration.customMappers, customOptionSelectorDetectors = sessionReplayConfiguration.customOptionSelectorDetectors, sampleRate = sessionReplayConfiguration.sampleRate, - startRecordingImmediately = startRecordingImmediately + startRecordingImmediately = sessionReplayConfiguration.startRecordingImmediately ) sdkCore.registerFeature(sessionReplayFeature) @@ -60,7 +57,7 @@ object SessionReplay { it.unwrap() as? SessionReplayFeature } - sessionReplayFeature?.startRecording() + sessionReplayFeature?.manuallyStartRecording() } /** @@ -76,6 +73,6 @@ object SessionReplay { it.unwrap() as? SessionReplayFeature } - sessionReplayFeature?.stopRecording() + sessionReplayFeature?.manuallyStopRecording() } } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt index 0f7905a650..ff7471752e 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt @@ -19,7 +19,8 @@ data class SessionReplayConfiguration internal constructor( internal val customMappers: List>, internal val customOptionSelectorDetectors: List, internal val sampleRate: Float, - internal val imagePrivacy: ImagePrivacy + internal val imagePrivacy: ImagePrivacy, + internal val startRecordingImmediately: Boolean ) { /** @@ -31,6 +32,7 @@ data class SessionReplayConfiguration internal constructor( private var customEndpointUrl: String? = null private var privacy = SessionReplayPrivacy.MASK private var imagePrivacy = ImagePrivacy.MASK_LARGE_ONLY + private var startRecordingImmediately = true private var extensionSupport: ExtensionSupport = NoOpExtensionSupport() /** @@ -76,6 +78,16 @@ data class SessionReplayConfiguration internal constructor( return this } + /** + * Should recording start automatically (or be manually started). + * If not specified then by default it starts automatically. + * @param enabled whether recording should start automatically or not. + */ + fun startRecordingImmediately(enabled: Boolean): Builder { + this.startRecordingImmediately = enabled + return this + } + /** * Builds a [SessionReplayConfiguration] based on the current state of this Builder. */ @@ -86,7 +98,8 @@ data class SessionReplayConfiguration internal constructor( imagePrivacy = imagePrivacy, customMappers = customMappers(), customOptionSelectorDetectors = extensionSupport.getOptionSelectorDetectors(), - sampleRate = sampleRate + sampleRate = sampleRate, + startRecordingImmediately = startRecordingImmediately ) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index 2a94054c73..dfc72d2fc1 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -77,6 +77,7 @@ internal class SessionReplayFeature( ) private lateinit var appContext: Context + private var manualRecordingWasStarted = AtomicBoolean(false) private var isRecording = AtomicBoolean(false) internal var sessionReplayRecorder: Recorder = NoOpRecorder() internal var dataWriter: RecordWriter = NoOpRecordWriter() @@ -161,6 +162,20 @@ internal class SessionReplayFeature( // endregion + // region Manual Recording + + internal fun manuallyStopRecording() { + manualRecordingWasStarted.set(false) + stopRecording() + } + + internal fun manuallyStartRecording() { + manualRecordingWasStarted.set(true) + startRecording() + } + + // endregion + // region Internal private fun handleRumSession(sessionMetadata: Map<*, *>) { @@ -205,16 +220,18 @@ internal class SessionReplayFeature( return } - val shouldRecord = startRecordingImmediately || isRecording.get() + val sessionReplayRecordingEnabled = startRecordingImmediately || manualRecordingWasStarted.get() - if (shouldRecord && keepSession && rateBasedSampler.sample()) { + if (sessionReplayRecordingEnabled && keepSession && rateBasedSampler.sample()) { startRecording() } else { - sdkCore.internalLogger.log( - InternalLogger.Level.INFO, - InternalLogger.Target.USER, - { SESSION_SAMPLED_OUT_MESSAGE } - ) + if (sessionReplayRecordingEnabled) { + sdkCore.internalLogger.log( + InternalLogger.Level.INFO, + InternalLogger.Target.USER, + { SESSION_SAMPLED_OUT_MESSAGE } + ) + } stopRecording() } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt index 51aa3f2ff0..a3af8e8da8 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt @@ -120,6 +120,17 @@ internal class SessionReplayConfigurationBuilderTest { assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) } + @Test + fun `M pass startRecordingImmediately W startRecordingImmediately`() { + // When + val sessionReplayConfiguration = testedBuilder + .startRecordingImmediately(true) + .build() + + // Then + assertThat(sessionReplayConfiguration.startRecordingImmediately).isTrue() + } + @Test fun `M use the provided custom Mappers W addExtensionSupport()`() { // Given diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt index 0c78a82185..554d4005d1 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayTest.kt @@ -69,7 +69,7 @@ internal class SessionReplayTest { } @Test - fun `M call startRecording on feature W startRecording`( + fun `M call manuallyStartRecording on feature W startRecording`( @Mock mockFeatureScope: FeatureScope, @Mock mockSessionReplayFeature: SessionReplayFeature ) { @@ -83,11 +83,11 @@ internal class SessionReplayTest { SessionReplay.startRecording(mockSdkCore) // Then - verify(mockSessionReplayFeature).startRecording() + verify(mockSessionReplayFeature).manuallyStartRecording() } @Test - fun `M call stopRecording on feature W stopRecording`( + fun `M call manuallyStopRecording on feature W stopRecording`( @Mock mockFeatureScope: FeatureScope, @Mock mockSessionReplayFeature: SessionReplayFeature ) { @@ -101,6 +101,6 @@ internal class SessionReplayTest { SessionReplay.stopRecording(mockSdkCore) // Then - verify(mockSessionReplayFeature).stopRecording() + verify(mockSessionReplayFeature).manuallyStopRecording() } } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt index 434398c648..fae60aa226 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt @@ -21,6 +21,7 @@ class SessionReplayConfigurationForgeryFactory : ForgeryFactory mockRecorder } + testedFeature.onInitialize(fakeContext) + testedFeature.manuallyStartRecording() + + // Then + verify(mockRecorder).resumeRecorders() + } + + @Test + fun `M call stopRecorders W manuallyStopRecording { if already recording }`( + @Mock fakeContext: Application, + @StringForgery fakeSessionId: String + ) { + // Given + val event = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId + ) + + whenever(mockSampler.sample()).thenReturn(true) + + // When + testedFeature = SessionReplayFeature( + sdkCore = mockSdkCore, + customEndpointUrl = fakeConfiguration.customEndpointUrl, + privacy = fakeConfiguration.privacy, + imagePrivacy = fakeConfiguration.imagePrivacy, + startRecordingImmediately = true, + rateBasedSampler = mockSampler + ) { _, _, _, _ -> mockRecorder } + testedFeature.onInitialize(fakeContext) + testedFeature.onReceive(event) + testedFeature.manuallyStopRecording() + + // Then + verify(mockRecorder).stopRecorders() + } + + // endregion + companion object { val appContext = ApplicationContextTestConfiguration(Application::class.java) From 24331d3f3031ce4b9161fea9b7f9b5220a7b5898 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Tue, 13 Aug 2024 14:28:56 +0300 Subject: [PATCH 003/111] RUM-5540: Refactor to allow recording state changes to current session --- .../internal/TelemetryEventHandler.kt | 4 +- .../internal/SessionReplayFeature.kt | 75 ++++++-- .../internal/SessionReplayFeatureTest.kt | 162 +++++++++++++++++- 3 files changed, 219 insertions(+), 22 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 3add64ce4f..7e9a6a84d9 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -250,7 +250,7 @@ internal class TelemetryEventHandler( sdkCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME) val sessionReplaySampleRate = sessionReplayFeatureContext[SESSION_REPLAY_SAMPLE_RATE_KEY] as? Long - val startSessionReplayImmediately = + val startRecordingImmediately = sessionReplayFeatureContext[SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY] as? Boolean val sessionReplayPrivacy = sessionReplayFeatureContext[SESSION_REPLAY_PRIVACY_KEY] as? String @@ -315,7 +315,7 @@ internal class TelemetryEventHandler( trackNetworkRequests = trackNetworkRequests, sessionReplaySampleRate = sessionReplaySampleRate, defaultPrivacyLevel = sessionReplayPrivacy, - startRecordingImmediately = startSessionReplayImmediately, + startRecordingImmediately = startRecordingImmediately, batchProcessingLevel = coreConfiguration.batchProcessingLevel.toLong() ) ) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index dfc72d2fc1..e7cdbd7114 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicReference /** * Session Replay feature class, which needs to be registered with Datadog SDK instance. */ +@Suppress("TooManyFunctions") internal class SessionReplayFeature( private val sdkCore: FeatureSdkCore, private val customEndpointUrl: String?, @@ -77,8 +78,10 @@ internal class SessionReplayFeature( ) private lateinit var appContext: Context - private var manualRecordingWasStarted = AtomicBoolean(false) + private var shouldRecord = AtomicBoolean(false) + private var recordingStateChanged = AtomicBoolean(false) private var isRecording = AtomicBoolean(false) + private var isSessionSampledIn = AtomicBoolean(false) internal var sessionReplayRecorder: Recorder = NoOpRecorder() internal var dataWriter: RecordWriter = NoOpRecordWriter() internal val initialized = AtomicBoolean(false) @@ -97,6 +100,9 @@ internal class SessionReplayFeature( return } + if (startRecordingImmediately) { + shouldRecord.set(true) + } this.appContext = appContext sdkCore.setEventReceiver(SESSION_REPLAY_FEATURE_NAME, this) @@ -165,13 +171,17 @@ internal class SessionReplayFeature( // region Manual Recording internal fun manuallyStopRecording() { - manualRecordingWasStarted.set(false) - stopRecording() + if (shouldRecord.get()) { + recordingStateChanged.set(true) + shouldRecord.set(false) + } } internal fun manuallyStartRecording() { - manualRecordingWasStarted.set(true) - startRecording() + if (!shouldRecord.get()) { + recordingStateChanged.set(true) + shouldRecord.set(true) + } } // endregion @@ -182,7 +192,12 @@ internal class SessionReplayFeature( if (sessionMetadata[SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY] == RUM_SESSION_RENEWED_BUS_MESSAGE ) { - checkStatusAndApplySample(sessionMetadata) + parseSessionMetadata(sessionMetadata) + ?.let { sessionData -> + if (shouldHandleEvent(sessionData)) { + handleRecording(sessionData) + } + } } else { sdkCore.internalLogger.log( InternalLogger.Level.WARN, @@ -197,8 +212,12 @@ internal class SessionReplayFeature( } } - @Suppress("ReturnCount") - private fun checkStatusAndApplySample(sessionMetadata: Map<*, *>) { + private data class SessionData( + val keepSession: Boolean, + val sessionId: String + ) + + private fun parseSessionMetadata(sessionMetadata: Map<*, *>): SessionData? { val keepSession = sessionMetadata[RUM_KEEP_SESSION_BUS_MESSAGE_KEY] as? Boolean val sessionId = sessionMetadata[RUM_SESSION_ID_BUS_MESSAGE_KEY] as? String @@ -208,34 +227,52 @@ internal class SessionReplayFeature( InternalLogger.Target.USER, { EVENT_MISSING_MANDATORY_FIELDS } ) - return + return null } - if (currentRumSessionId.get() == sessionId) { - // we already handled this session - return + return SessionData(keepSession, sessionId) + } + + @Suppress("ReturnCount") + private fun shouldHandleEvent(sessionData: SessionData): Boolean { + if (currentRumSessionId.get() != sessionData.sessionId) { + // sessionId changed so resample + isSessionSampledIn.set(rateBasedSampler.sample()) + } else { + if (!recordingStateChanged.get()) { + // sessionId was already seen and recordingState did not change + return false + } } if (!checkIfInitialized()) { - return + return false } - val sessionReplayRecordingEnabled = startRecordingImmediately || manualRecordingWasStarted.get() + val isSampledIn = sessionData.keepSession && isSessionSampledIn.get() - if (sessionReplayRecordingEnabled && keepSession && rateBasedSampler.sample()) { - startRecording() - } else { - if (sessionReplayRecordingEnabled) { + if (!isSampledIn) { + if (shouldRecord.getAndSet(false)) { sdkCore.internalLogger.log( InternalLogger.Level.INFO, InternalLogger.Target.USER, { SESSION_SAMPLED_OUT_MESSAGE } ) } + } + + recordingStateChanged.set(false) + return true + } + + private fun handleRecording(sessionData: SessionData) { + if (shouldRecord.get()) { + startRecording() + } else { stopRecording() } - currentRumSessionId.set(sessionId) + currentRumSessionId.set(sessionData.sessionId) } private fun checkIfInitialized(): Boolean { diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt index 944f76a889..a52057dc1a 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt @@ -34,6 +34,9 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings @@ -43,6 +46,7 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions @@ -54,6 +58,7 @@ import java.util.UUID import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit +import java.util.stream.Stream @Extensions( ExtendWith(MockitoExtension::class), @@ -962,13 +967,89 @@ internal class SessionReplayFeatureTest { .isEqualTo(SessionReplayFeature.STORAGE_CONFIGURATION) } - // region manual recording + // region startRecordingImmediately + + @ParameterizedTest + @MethodSource("recordingScenarioProvider") + fun `M start recording W startRecordingImmediately`( + scenario: SessionRecordingScenario + ) { + // Given + val fakeContext = mock() + val event = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to scenario.keepSession, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId + ) + + whenever(mockSampler.sample()).thenReturn(scenario.sampleInSession) + + // When + testedFeature = SessionReplayFeature( + sdkCore = mockSdkCore, + customEndpointUrl = fakeConfiguration.customEndpointUrl, + privacy = fakeConfiguration.privacy, + imagePrivacy = fakeConfiguration.imagePrivacy, + startRecordingImmediately = scenario.startRecordingImmediately, + rateBasedSampler = mockSampler + ) { _, _, _, _ -> mockRecorder } + testedFeature.onInitialize(fakeContext) + testedFeature.onReceive(event) + + // Then + if (scenario.expectedResult) { + verify(mockRecorder).resumeRecorders() + } else { + verify(mockRecorder, never()).resumeRecorders() + } + } + + // endregion + + // region manual stop/start + + @Test + fun `M start recorders only once W onReceive { sessionId is the same and recordingState did not change }`( + @Mock fakeContext: Application + ) { + // Given + val event = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId + ) + whenever(mockSampler.sample()).thenReturn(true) + + // When + testedFeature = SessionReplayFeature( + sdkCore = mockSdkCore, + customEndpointUrl = fakeConfiguration.customEndpointUrl, + privacy = fakeConfiguration.privacy, + imagePrivacy = fakeConfiguration.imagePrivacy, + startRecordingImmediately = true, + rateBasedSampler = mockSampler + ) { _, _, _, _ -> mockRecorder } + testedFeature.onInitialize(fakeContext) + testedFeature.onReceive(event) + testedFeature.onReceive(event) + + // Then + verify(mockRecorder, times(1)).resumeRecorders() + } @Test fun `M call resumeRecorders W manuallyStartRecording`( @Mock fakeContext: Application ) { // Given + val event = mapOf( + SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to + SessionReplayFeature.RUM_SESSION_RENEWED_BUS_MESSAGE, + SessionReplayFeature.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true, + SessionReplayFeature.RUM_SESSION_ID_BUS_MESSAGE_KEY to fakeSessionId + ) whenever(mockSampler.sample()).thenReturn(true) // When @@ -982,6 +1063,7 @@ internal class SessionReplayFeatureTest { ) { _, _, _, _ -> mockRecorder } testedFeature.onInitialize(fakeContext) testedFeature.manuallyStartRecording() + testedFeature.onReceive(event) // Then verify(mockRecorder).resumeRecorders() @@ -1014,6 +1096,7 @@ internal class SessionReplayFeatureTest { testedFeature.onInitialize(fakeContext) testedFeature.onReceive(event) testedFeature.manuallyStopRecording() + testedFeature.onReceive(event) // Then verify(mockRecorder).stopRecorders() @@ -1021,6 +1104,13 @@ internal class SessionReplayFeatureTest { // endregion + internal data class SessionRecordingScenario( + val startRecordingImmediately: Boolean, + val keepSession: Boolean, + val sampleInSession: Boolean, + val expectedResult: Boolean + ) + companion object { val appContext = ApplicationContextTestConfiguration(Application::class.java) @@ -1030,5 +1120,75 @@ internal class SessionReplayFeatureTest { fun getTestConfigurations(): List { return listOf(appContext) } + + @JvmStatic + fun recordingScenarioProvider(): Stream { + return Stream.of( + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = true, + keepSession = true, + sampleInSession = true, + expectedResult = true + ) + ), + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = true, + keepSession = false, + sampleInSession = true, + expectedResult = false + ) + ), + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = true, + keepSession = true, + sampleInSession = false, + expectedResult = false + ) + ), + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = true, + keepSession = false, + sampleInSession = false, + expectedResult = false + ) + ), + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = false, + keepSession = true, + sampleInSession = true, + expectedResult = false + ) + ), + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = false, + keepSession = false, + sampleInSession = true, + expectedResult = false + ) + ), + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = false, + keepSession = true, + sampleInSession = false, + expectedResult = false + ) + ), + Arguments.of( + SessionRecordingScenario( + startRecordingImmediately = false, + keepSession = false, + sampleInSession = false, + expectedResult = false + ) + ) + ) + } } } From 95d691c49a76f08bcc7925b161a07c59af7bf1bd Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:51:35 +0300 Subject: [PATCH 004/111] rum-5540: refactor SessionReplayFeature --- .../internal/SessionReplayFeature.kt | 112 +++++++++++------- .../internal/SessionReplayFeatureTest.kt | 32 +++-- 2 files changed, 88 insertions(+), 56 deletions(-) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index e7cdbd7114..47327d8824 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -78,10 +78,20 @@ internal class SessionReplayFeature( ) private lateinit var appContext: Context - private var shouldRecord = AtomicBoolean(false) + + // should we record the session - a combination of rum sampling, sr sampling + // and sr stop/start state + private var shouldRecord = AtomicBoolean(startRecordingImmediately) + + // used to monitor changes to an active session due to manual stop/start private var recordingStateChanged = AtomicBoolean(false) + + // are we recording at the moment private var isRecording = AtomicBoolean(false) + + // is the current session sampled in private var isSessionSampledIn = AtomicBoolean(false) + internal var sessionReplayRecorder: Recorder = NoOpRecorder() internal var dataWriter: RecordWriter = NoOpRecordWriter() internal val initialized = AtomicBoolean(false) @@ -92,17 +102,10 @@ internal class SessionReplayFeature( override fun onInitialize(appContext: Context) { if (appContext !is Application) { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.USER, - { REQUIRES_APPLICATION_CONTEXT_WARN_MESSAGE } - ) + logMissingApplicationContextError() return } - if (startRecordingImmediately) { - shouldRecord.set(true) - } this.appContext = appContext sdkCore.setEventReceiver(SESSION_REPLAY_FEATURE_NAME, this) @@ -163,6 +166,10 @@ internal class SessionReplayFeature( return } + if (!checkIfInitialized()) { + return + } + handleRumSession(event) } @@ -171,16 +178,14 @@ internal class SessionReplayFeature( // region Manual Recording internal fun manuallyStopRecording() { - if (shouldRecord.get()) { + if (shouldRecord.compareAndSet(true, false)) { recordingStateChanged.set(true) - shouldRecord.set(false) } } internal fun manuallyStartRecording() { - if (!shouldRecord.get()) { + if (shouldRecord.compareAndSet(false, true)) { recordingStateChanged.set(true) - shouldRecord.set(true) } } @@ -194,7 +199,10 @@ internal class SessionReplayFeature( ) { parseSessionMetadata(sessionMetadata) ?.let { sessionData -> - if (shouldHandleEvent(sessionData)) { + val alreadySeenSession = currentRumSessionId.get() == sessionData.sessionId + if (shouldHandleSession(alreadySeenSession)) { + applySampling(alreadySeenSession) + modifyShouldRecordState(sessionData) handleRecording(sessionData) } } @@ -222,47 +230,62 @@ internal class SessionReplayFeature( val sessionId = sessionMetadata[RUM_SESSION_ID_BUS_MESSAGE_KEY] as? String if (keepSession == null || sessionId == null) { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.USER, - { EVENT_MISSING_MANDATORY_FIELDS } - ) + logEventMissingMandatoryFieldsError() return null } return SessionData(keepSession, sessionId) } - @Suppress("ReturnCount") - private fun shouldHandleEvent(sessionData: SessionData): Boolean { - if (currentRumSessionId.get() != sessionData.sessionId) { - // sessionId changed so resample - isSessionSampledIn.set(rateBasedSampler.sample()) - } else { - if (!recordingStateChanged.get()) { - // sessionId was already seen and recordingState did not change - return false - } - } + private fun shouldHandleSession(alreadySeenSession: Boolean): Boolean { + return !alreadySeenSession || recordingStateChanged.get() + } - if (!checkIfInitialized()) { - return false + private fun applySampling(alreadySeenSession: Boolean) { + if (!alreadySeenSession) { + isSessionSampledIn.set(rateBasedSampler.sample()) } + } + private fun modifyShouldRecordState(sessionData: SessionData) { val isSampledIn = sessionData.keepSession && isSessionSampledIn.get() - if (!isSampledIn) { - if (shouldRecord.getAndSet(false)) { - sdkCore.internalLogger.log( - InternalLogger.Level.INFO, - InternalLogger.Target.USER, - { SESSION_SAMPLED_OUT_MESSAGE } - ) + if (shouldRecord.compareAndSet(true, false)) { + logSampledOutMessage() } } + } - recordingStateChanged.set(false) - return true + private fun logMissingApplicationContextError() { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { REQUIRES_APPLICATION_CONTEXT_WARN_MESSAGE } + ) + } + + private fun logEventMissingMandatoryFieldsError() { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { EVENT_MISSING_MANDATORY_FIELDS } + ) + } + + private fun logSampledOutMessage() { + sdkCore.internalLogger.log( + InternalLogger.Level.INFO, + InternalLogger.Target.USER, + { SESSION_SAMPLED_OUT_MESSAGE } + ) + } + + private fun logNotInitializedError() { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { CANNOT_START_RECORDING_NOT_INITIALIZED } + ) } private fun handleRecording(sessionData: SessionData) { @@ -272,16 +295,13 @@ internal class SessionReplayFeature( stopRecording() } + recordingStateChanged.set(false) currentRumSessionId.set(sessionData.sessionId) } private fun checkIfInitialized(): Boolean { if (!initialized.get()) { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.USER, - { CANNOT_START_RECORDING_NOT_INITIALIZED } - ) + logNotInitializedError() return false } return true diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt index a52057dc1a..f717419771 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt @@ -829,7 +829,8 @@ internal class SessionReplayFeatureTest { @Test fun `M log warning and do nothing W onReceive() { unknown type property value }`( - forge: Forge + forge: Forge, + @Mock fakeContext: Application ) { // Given val event = mapOf( @@ -838,6 +839,7 @@ internal class SessionReplayFeatureTest { ) // When + testedFeature.onInitialize(fakeContext) testedFeature.onReceive(event) // Then @@ -849,11 +851,13 @@ internal class SessionReplayFeatureTest { expectedMessage ) - verifyNoInteractions(mockRecorder) + verify(mockRecorder, never()).resumeRecorders() } @Test - fun `M log warning and do nothing W onReceive() { missing mandatory fields }`() { + fun `M log warning and do nothing W onReceive() { missing mandatory fields }`( + @Mock fakeContext: Application + ) { // Given val event = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to @@ -861,6 +865,7 @@ internal class SessionReplayFeatureTest { ) // When + testedFeature.onInitialize(fakeContext) testedFeature.onReceive(event) // Then @@ -870,11 +875,13 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.EVENT_MISSING_MANDATORY_FIELDS ) - verifyNoInteractions(mockRecorder) + verify(mockRecorder, never()).resumeRecorders() } @Test - fun `M log warning and do nothing W onReceive() { missing keep state field }`() { + fun `M log warning and do nothing W onReceive() { missing keep state field }`( + @Mock fakeContext: Application + ) { // Given val event = mapOf( SessionReplayFeature.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to @@ -883,6 +890,7 @@ internal class SessionReplayFeatureTest { ) // When + testedFeature.onInitialize(fakeContext) testedFeature.onReceive(event) // Then @@ -892,12 +900,13 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.EVENT_MISSING_MANDATORY_FIELDS ) - verifyNoInteractions(mockRecorder) + verify(mockRecorder, never()).resumeRecorders() } @Test fun `M log warning and do nothing W onReceive() { missing session id field }`( - @BoolForgery fakeKeep: Boolean + @BoolForgery fakeKeep: Boolean, + @Mock fakeContext: Application ) { // Given val event = mapOf( @@ -907,6 +916,7 @@ internal class SessionReplayFeatureTest { ) // When + testedFeature.onInitialize(fakeContext) testedFeature.onReceive(event) // Then @@ -916,12 +926,13 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.EVENT_MISSING_MANDATORY_FIELDS ) - verifyNoInteractions(mockRecorder) + verify(mockRecorder, never()).resumeRecorders() } @Test fun `M log warning and do nothing W onReceive() { mandatory fields have wrong format }`( - forge: Forge + forge: Forge, + @Mock fakeContext: Application ) { // Given val event = mapOf( @@ -934,6 +945,7 @@ internal class SessionReplayFeatureTest { ) // When + testedFeature.onInitialize(fakeContext) testedFeature.onReceive(event) // Then @@ -943,7 +955,7 @@ internal class SessionReplayFeatureTest { SessionReplayFeature.EVENT_MISSING_MANDATORY_FIELDS ) - verifyNoInteractions(mockRecorder) + verify(mockRecorder, never()).resumeRecorders() } @Test From 2266b724d96da35f1b92971c7fb651e50f7d0e89 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:01:57 +0300 Subject: [PATCH 005/111] Make Image Privacy a public api --- features/dd-sdk-android-session-replay/api/apiSurface | 1 + .../api/dd-sdk-android-session-replay.api | 1 + .../android/sessionreplay/SessionReplayConfiguration.kt | 4 ++-- .../sessionreplay/SessionReplayConfigurationBuilderTest.kt | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/dd-sdk-android-session-replay/api/apiSurface b/features/dd-sdk-android-session-replay/api/apiSurface index dae4fabe0f..feb8c9b9d4 100644 --- a/features/dd-sdk-android-session-replay/api/apiSurface +++ b/features/dd-sdk-android-session-replay/api/apiSurface @@ -19,6 +19,7 @@ data class com.datadog.android.sessionreplay.SessionReplayConfiguration fun addExtensionSupport(ExtensionSupport): Builder fun useCustomEndpoint(String): Builder fun setPrivacy(SessionReplayPrivacy): Builder + fun setImagePrivacy(ImagePrivacy): Builder fun startRecordingImmediately(Boolean): Builder fun build(): SessionReplayConfiguration enum com.datadog.android.sessionreplay.SessionReplayPrivacy diff --git a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api index 1eb9552874..ce2397cf61 100644 --- a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api +++ b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api @@ -45,6 +45,7 @@ public final class com/datadog/android/sessionreplay/SessionReplayConfiguration$ public fun (F)V public final fun addExtensionSupport (Lcom/datadog/android/sessionreplay/ExtensionSupport;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun build ()Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public final fun setImagePrivacy (Lcom/datadog/android/sessionreplay/ImagePrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun setPrivacy (Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun startRecordingImmediately (Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun useCustomEndpoint (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt index ff7471752e..f04795f42c 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt @@ -31,7 +31,7 @@ data class SessionReplayConfiguration internal constructor( class Builder(@FloatRange(from = 0.0, to = 100.0) private val sampleRate: Float) { private var customEndpointUrl: String? = null private var privacy = SessionReplayPrivacy.MASK - private var imagePrivacy = ImagePrivacy.MASK_LARGE_ONLY + private var imagePrivacy = ImagePrivacy.MASK_ALL private var startRecordingImmediately = true private var extensionSupport: ExtensionSupport = NoOpExtensionSupport() @@ -73,7 +73,7 @@ data class SessionReplayConfiguration internal constructor( * @see ImagePrivacy.MASK_LARGE_ONLY * @see ImagePrivacy.MASK_ALL */ - internal fun setImagePrivacy(level: ImagePrivacy): Builder { + fun setImagePrivacy(level: ImagePrivacy): Builder { this.imagePrivacy = level return this } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt index a3af8e8da8..134ab1684a 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt @@ -59,7 +59,7 @@ internal class SessionReplayConfigurationBuilderTest { // Then assertThat(sessionReplayConfiguration.customEndpointUrl).isNull() assertThat(sessionReplayConfiguration.privacy).isEqualTo(SessionReplayPrivacy.MASK) - assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_LARGE_ONLY) + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) assertThat(sessionReplayConfiguration.customMappers).isEmpty() assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) @@ -78,7 +78,7 @@ internal class SessionReplayConfigurationBuilderTest { assertThat(sessionReplayConfiguration.customEndpointUrl) .isEqualTo(sessionReplayUrl) assertThat(sessionReplayConfiguration.privacy).isEqualTo(SessionReplayPrivacy.MASK) - assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_LARGE_ONLY) + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) assertThat(sessionReplayConfiguration.customMappers).isEmpty() assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) @@ -96,7 +96,7 @@ internal class SessionReplayConfigurationBuilderTest { // Then assertThat(sessionReplayConfiguration.customEndpointUrl).isNull() assertThat(sessionReplayConfiguration.privacy).isEqualTo(fakePrivacy) - assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_LARGE_ONLY) + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) assertThat(sessionReplayConfiguration.customMappers).isEmpty() assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) From 52c56c2804edeefe344dcf81cac7597f3e20fe1b Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:26:51 +0300 Subject: [PATCH 006/111] rum-5763: Add TouchPrivacy --- .../api/apiSurface | 4 + .../api/dd-sdk-android-session-replay.api | 12 ++- .../android/sessionreplay/SessionReplay.kt | 1 + .../SessionReplayConfiguration.kt | 16 +++- .../android/sessionreplay/TouchPrivacy.kt | 24 +++++ .../internal/DefaultRecorderProvider.kt | 3 + .../internal/SessionReplayFeature.kt | 7 +- .../recorder/SessionReplayRecorder.kt | 9 +- .../recorder/WindowCallbackInterceptor.kt | 7 +- .../callback/RecorderWindowCallback.kt | 18 ++-- .../SessionReplayConfigurationBuilderTest.kt | 27 ++++-- .../SessionReplayRecorderTest.kt | 4 + ...essionReplayConfigurationForgeryFactory.kt | 2 + .../internal/SessionReplayFeatureTest.kt | 8 ++ .../recorder/WindowCallbackInterceptorTest.kt | 7 +- .../callback/RecorderWindowCallbackTest.kt | 93 ++++++++++++++++++- .../DefaultImageWireframeHelperTest.kt | 4 +- 17 files changed, 215 insertions(+), 31 deletions(-) create mode 100644 features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TouchPrivacy.kt diff --git a/features/dd-sdk-android-session-replay/api/apiSurface b/features/dd-sdk-android-session-replay/api/apiSurface index feb8c9b9d4..469bc98cc0 100644 --- a/features/dd-sdk-android-session-replay/api/apiSurface +++ b/features/dd-sdk-android-session-replay/api/apiSurface @@ -20,12 +20,16 @@ data class com.datadog.android.sessionreplay.SessionReplayConfiguration fun useCustomEndpoint(String): Builder fun setPrivacy(SessionReplayPrivacy): Builder fun setImagePrivacy(ImagePrivacy): Builder + fun setTouchPrivacy(TouchPrivacy): Builder fun startRecordingImmediately(Boolean): Builder fun build(): SessionReplayConfiguration enum com.datadog.android.sessionreplay.SessionReplayPrivacy - ALLOW - MASK - MASK_USER_INPUT +enum com.datadog.android.sessionreplay.TouchPrivacy + - SHOW + - HIDE data class com.datadog.android.sessionreplay.recorder.MappingContext constructor(SystemInformation, com.datadog.android.sessionreplay.utils.ImageWireframeHelper, com.datadog.android.sessionreplay.SessionReplayPrivacy, com.datadog.android.sessionreplay.ImagePrivacy, Boolean = false) interface com.datadog.android.sessionreplay.recorder.OptionSelectorDetector diff --git a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api index ce2397cf61..3869e0667e 100644 --- a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api +++ b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api @@ -34,8 +34,8 @@ public final class com/datadog/android/sessionreplay/SessionReplay { } public final class com/datadog/android/sessionreplay/SessionReplayConfiguration { - public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; - public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -47,6 +47,7 @@ public final class com/datadog/android/sessionreplay/SessionReplayConfiguration$ public final fun build ()Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; public final fun setImagePrivacy (Lcom/datadog/android/sessionreplay/ImagePrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun setPrivacy (Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; + public final fun setTouchPrivacy (Lcom/datadog/android/sessionreplay/TouchPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun startRecordingImmediately (Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun useCustomEndpoint (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; } @@ -59,6 +60,13 @@ public final class com/datadog/android/sessionreplay/SessionReplayPrivacy : java public static fun values ()[Lcom/datadog/android/sessionreplay/SessionReplayPrivacy; } +public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/Enum { + public static final field HIDE Lcom/datadog/android/sessionreplay/TouchPrivacy; + public static final field SHOW Lcom/datadog/android/sessionreplay/TouchPrivacy; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/TouchPrivacy; + public static fun values ()[Lcom/datadog/android/sessionreplay/TouchPrivacy; +} + public final class com/datadog/android/sessionreplay/model/MobileSegment { public static final field Companion Lcom/datadog/android/sessionreplay/model/MobileSegment$Companion; public fun (Lcom/datadog/android/sessionreplay/model/MobileSegment$Application;Lcom/datadog/android/sessionreplay/model/MobileSegment$Session;Lcom/datadog/android/sessionreplay/model/MobileSegment$View;JJJLjava/lang/Long;Ljava/lang/Boolean;Lcom/datadog/android/sessionreplay/model/MobileSegment$Source;Ljava/util/List;)V diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt index fa48f432ef..e1cbea858c 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt @@ -35,6 +35,7 @@ object SessionReplay { customEndpointUrl = sessionReplayConfiguration.customEndpointUrl, privacy = sessionReplayConfiguration.privacy, imagePrivacy = sessionReplayConfiguration.imagePrivacy, + touchPrivacy = sessionReplayConfiguration.touchPrivacy, customMappers = sessionReplayConfiguration.customMappers, customOptionSelectorDetectors = sessionReplayConfiguration.customOptionSelectorDetectors, sampleRate = sessionReplayConfiguration.sampleRate, diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt index f04795f42c..82774d1a4c 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt @@ -20,7 +20,8 @@ data class SessionReplayConfiguration internal constructor( internal val customOptionSelectorDetectors: List, internal val sampleRate: Float, internal val imagePrivacy: ImagePrivacy, - internal val startRecordingImmediately: Boolean + internal val startRecordingImmediately: Boolean, + internal val touchPrivacy: TouchPrivacy ) { /** @@ -33,6 +34,7 @@ data class SessionReplayConfiguration internal constructor( private var privacy = SessionReplayPrivacy.MASK private var imagePrivacy = ImagePrivacy.MASK_ALL private var startRecordingImmediately = true + private var touchPrivacy = TouchPrivacy.HIDE private var extensionSupport: ExtensionSupport = NoOpExtensionSupport() /** @@ -78,6 +80,17 @@ data class SessionReplayConfiguration internal constructor( return this } + /** + * Sets the touch recording level for the Session Replay feature. + * If not specified then all touches will be hidden by default. + * @see TouchPrivacy.HIDE + * @see TouchPrivacy.SHOW + */ + fun setTouchPrivacy(level: TouchPrivacy): Builder { + this.touchPrivacy = level + return this + } + /** * Should recording start automatically (or be manually started). * If not specified then by default it starts automatically. @@ -96,6 +109,7 @@ data class SessionReplayConfiguration internal constructor( customEndpointUrl = customEndpointUrl, privacy = privacy, imagePrivacy = imagePrivacy, + touchPrivacy = touchPrivacy, customMappers = customMappers(), customOptionSelectorDetectors = extensionSupport.getOptionSelectorDetectors(), sampleRate = sampleRate, diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TouchPrivacy.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TouchPrivacy.kt new file mode 100644 index 0000000000..5c7eeb1d1f --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TouchPrivacy.kt @@ -0,0 +1,24 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay + +/** + * Defines the Session Replay privacy policy when recording touch interactions. + * @see TouchPrivacy.SHOW + * @see TouchPrivacy.HIDE + */ +enum class TouchPrivacy { + /** + * All touch interactions will be recorded. + */ + SHOW, + + /** + * No touch interactions will be recorded. + */ + HIDE +} diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt index dd0cbf63cb..c8a9f66011 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt @@ -25,6 +25,7 @@ import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.MapperTypeWrapper import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.recorder.Recorder import com.datadog.android.sessionreplay.internal.recorder.SessionReplayRecorder import com.datadog.android.sessionreplay.internal.recorder.mapper.ActionBarContainerMapper @@ -59,6 +60,7 @@ internal class DefaultRecorderProvider( private val sdkCore: FeatureSdkCore, private val privacy: SessionReplayPrivacy, private val imagePrivacy: ImagePrivacy, + private val touchPrivacy: TouchPrivacy, private val customMappers: List>, private val customOptionSelectorDetectors: List ) : RecorderProvider { @@ -76,6 +78,7 @@ internal class DefaultRecorderProvider( rumContextProvider = SessionReplayRumContextProvider(sdkCore), privacy = privacy, imagePrivacy = imagePrivacy, + touchPrivacy = touchPrivacy, recordWriter = recordWriter, timeProvider = SessionReplayTimeProvider(sdkCore), mappers = customMappers + builtInMappers(), diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index 47327d8824..295b4aeadf 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -21,6 +21,7 @@ import com.datadog.android.core.sampling.Sampler import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.MapperTypeWrapper import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.net.BatchesToSegmentsMapper import com.datadog.android.sessionreplay.internal.net.SegmentRequestFactory import com.datadog.android.sessionreplay.internal.recorder.NoOpRecorder @@ -44,7 +45,8 @@ internal class SessionReplayFeature( private val sdkCore: FeatureSdkCore, private val customEndpointUrl: String?, internal val privacy: SessionReplayPrivacy, - internal val imagePrivacy: ImagePrivacy?, + internal val imagePrivacy: ImagePrivacy, + internal val touchPrivacy: TouchPrivacy, private val rateBasedSampler: Sampler, private val startRecordingImmediately: Boolean, private val recorderProvider: RecorderProvider @@ -57,6 +59,7 @@ internal class SessionReplayFeature( customEndpointUrl: String?, privacy: SessionReplayPrivacy, imagePrivacy: ImagePrivacy, + touchPrivacy: TouchPrivacy, customMappers: List>, customOptionSelectorDetectors: List, sampleRate: Float, @@ -66,12 +69,14 @@ internal class SessionReplayFeature( customEndpointUrl, privacy, imagePrivacy, + touchPrivacy, RateBasedSampler(sampleRate), startRecordingImmediately, DefaultRecorderProvider( sdkCore, privacy, imagePrivacy, + touchPrivacy, customMappers, customOptionSelectorDetectors ) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt index ceb5782cb8..8008f32f25 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt @@ -17,6 +17,7 @@ import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.MapperTypeWrapper import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.LifecycleCallback import com.datadog.android.sessionreplay.internal.SessionReplayLifecycleCallback import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler @@ -56,6 +57,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { private val rumContextProvider: RumContextProvider private val privacy: SessionReplayPrivacy private val imagePrivacy: ImagePrivacy + private val touchPrivacy: TouchPrivacy private val recordWriter: RecordWriter private val timeProvider: TimeProvider private val mappers: List> @@ -77,6 +79,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { rumContextProvider: RumContextProvider, privacy: SessionReplayPrivacy, imagePrivacy: ImagePrivacy, + touchPrivacy: TouchPrivacy, recordWriter: RecordWriter, timeProvider: TimeProvider, mappers: List> = emptyList(), @@ -105,6 +108,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { this.rumContextProvider = rumContextProvider this.privacy = privacy this.imagePrivacy = imagePrivacy + this.touchPrivacy = touchPrivacy this.recordWriter = recordWriter this.timeProvider = timeProvider this.mappers = mappers @@ -185,7 +189,8 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { timeProvider, internalLogger, privacy, - imagePrivacy + imagePrivacy, + touchPrivacy ) this.sessionReplayLifecycleCallback = SessionReplayLifecycleCallback(this) this.uiHandler = Handler(Looper.getMainLooper()) @@ -199,6 +204,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { rumContextProvider: RumContextProvider, privacy: SessionReplayPrivacy, imagePrivacy: ImagePrivacy, + touchPrivacy: TouchPrivacy, recordWriter: RecordWriter, timeProvider: TimeProvider, mappers: List> = emptyList(), @@ -216,6 +222,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { this.rumContextProvider = rumContextProvider this.privacy = privacy this.imagePrivacy = imagePrivacy + this.touchPrivacy = touchPrivacy this.recordWriter = recordWriter this.timeProvider = timeProvider this.mappers = mappers diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt index 047160a299..4f8465371a 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt @@ -11,6 +11,7 @@ import android.view.Window import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.recorder.callback.NoOpWindowCallback import com.datadog.android.sessionreplay.internal.recorder.callback.RecorderWindowCallback @@ -23,7 +24,8 @@ internal class WindowCallbackInterceptor( private val timeProvider: TimeProvider, private val internalLogger: InternalLogger, private val privacy: SessionReplayPrivacy, - private val imagePrivacy: ImagePrivacy + private val imagePrivacy: ImagePrivacy, + private val touchPrivacy: TouchPrivacy ) { private val wrappedWindows: WeakHashMap = WeakHashMap() @@ -58,7 +60,8 @@ internal class WindowCallbackInterceptor( viewOnDrawInterceptor, internalLogger, privacy, - imagePrivacy + imagePrivacy, + touchPrivacy ) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt index 376f899251..441c0b0b4d 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt @@ -13,6 +13,7 @@ import androidx.annotation.MainThread import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.recorder.ViewOnDrawInterceptor import com.datadog.android.sessionreplay.internal.recorder.WindowInspector @@ -32,6 +33,7 @@ internal class RecorderWindowCallback( private val internalLogger: InternalLogger, private val privacy: SessionReplayPrivacy, private val imagePrivacy: ImagePrivacy, + private val touchPrivacy: TouchPrivacy, private val copyEvent: (MotionEvent) -> MotionEvent = { @Suppress("UnsafeThirdPartyFunctionCall") // NPE cannot happen here MotionEvent.obtain(it) @@ -51,13 +53,15 @@ internal class RecorderWindowCallback( @MainThread override fun dispatchTouchEvent(event: MotionEvent?): Boolean { if (event != null) { - // we copy it and delegate it to the gesture detector for analysis - @Suppress("UnsafeThirdPartyFunctionCall") // internal safe call - val copy = copyEvent(event) - try { - handleEvent(copy) - } finally { - copy.recycle() + if (touchPrivacy == TouchPrivacy.SHOW) { + // we copy it and delegate it to the gesture detector for analysis + @Suppress("UnsafeThirdPartyFunctionCall") // internal safe call + val copy = copyEvent(event) + try { + handleEvent(copy) + } finally { + copy.recycle() + } } } else { internalLogger.log( diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt index 134ab1684a..004c5d8cf3 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt @@ -8,7 +8,6 @@ package com.datadog.android.sessionreplay import android.view.View import com.datadog.android.sessionreplay.forge.ForgeConfigurator -import com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper import fr.xgouchet.elmyr.annotation.FloatForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.StringForgery @@ -34,12 +33,11 @@ import org.mockito.quality.Strictness @ForgeConfiguration(value = ForgeConfigurator::class) internal class SessionReplayConfigurationBuilderTest { - lateinit var testedBuilder: SessionReplayConfiguration.Builder + private lateinit var testedBuilder: SessionReplayConfiguration.Builder @Mock lateinit var mockExtensionSupport: ExtensionSupport - lateinit var fakeCustomViewMappers: Map, WireframeMapper<*>> - lateinit var fakeExpectedCustomMappers: List> + private lateinit var fakeExpectedCustomMappers: List> @FloatForgery var fakeSampleRate: Float = 0f @@ -60,6 +58,7 @@ internal class SessionReplayConfigurationBuilderTest { assertThat(sessionReplayConfiguration.customEndpointUrl).isNull() assertThat(sessionReplayConfiguration.privacy).isEqualTo(SessionReplayPrivacy.MASK) assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(TouchPrivacy.HIDE) assertThat(sessionReplayConfiguration.customMappers).isEmpty() assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) @@ -79,6 +78,7 @@ internal class SessionReplayConfigurationBuilderTest { .isEqualTo(sessionReplayUrl) assertThat(sessionReplayConfiguration.privacy).isEqualTo(SessionReplayPrivacy.MASK) assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(TouchPrivacy.HIDE) assertThat(sessionReplayConfiguration.customMappers).isEmpty() assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) @@ -97,6 +97,7 @@ internal class SessionReplayConfigurationBuilderTest { assertThat(sessionReplayConfiguration.customEndpointUrl).isNull() assertThat(sessionReplayConfiguration.privacy).isEqualTo(fakePrivacy) assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(TouchPrivacy.HIDE) assertThat(sessionReplayConfiguration.customMappers).isEmpty() assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) @@ -112,12 +113,20 @@ internal class SessionReplayConfigurationBuilderTest { .build() // Then - assertThat(sessionReplayConfiguration.customEndpointUrl).isNull() - assertThat(sessionReplayConfiguration.privacy).isEqualTo(SessionReplayPrivacy.MASK) assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(fakeImagePrivacy) - assertThat(sessionReplayConfiguration.customMappers).isEmpty() - assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() - assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) + } + + @Test + fun `M use the given touch privacy rule W setTouchPrivacy`( + @Forgery fakeTouchPrivacy: TouchPrivacy + ) { + // When + val sessionReplayConfiguration = testedBuilder + .setTouchPrivacy(fakeTouchPrivacy) + .build() + + // Then + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(fakeTouchPrivacy) } @Test diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt index c730b4bb22..7bcb479ba4 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt @@ -65,6 +65,9 @@ internal class SessionReplayRecorderTest { @Forgery private lateinit var fakeImagePrivacy: ImagePrivacy + @Forgery + private lateinit var fakeTouchPrivacy: TouchPrivacy + @Mock private lateinit var mockTimeProvider: TimeProvider @@ -109,6 +112,7 @@ internal class SessionReplayRecorderTest { rumContextProvider = mockRumContextProvider, privacy = fakePrivacy, imagePrivacy = fakeImagePrivacy, + touchPrivacy = fakeTouchPrivacy, recordWriter = mockRecordWriter, timeProvider = mockTimeProvider, mappers = mock(), diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt index fae60aa226..dcc02db347 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt @@ -9,6 +9,7 @@ package com.datadog.android.sessionreplay.forge import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayConfiguration import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory import org.mockito.kotlin.mock @@ -19,6 +20,7 @@ class SessionReplayConfigurationForgeryFactory : ForgeryFactory mockRecorder } } @@ -129,6 +130,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + touchPrivacy = fakeConfiguration.touchPrivacy, customMappers = emptyList(), customOptionSelectorDetectors = emptyList(), startRecordingImmediately = true, @@ -151,6 +153,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + touchPrivacy = fakeConfiguration.touchPrivacy, customMappers = emptyList(), customOptionSelectorDetectors = emptyList(), sampleRate = fakeConfiguration.sampleRate, @@ -186,6 +189,7 @@ internal class SessionReplayFeatureTest { privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, startRecordingImmediately = true, + touchPrivacy = fakeConfiguration.touchPrivacy, rateBasedSampler = mockSampler ) { _, _, _, _ -> mockRecorder } @@ -1003,6 +1007,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + touchPrivacy = fakeConfiguration.touchPrivacy, startRecordingImmediately = scenario.startRecordingImmediately, rateBasedSampler = mockSampler ) { _, _, _, _ -> mockRecorder } @@ -1040,6 +1045,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + touchPrivacy = fakeConfiguration.touchPrivacy, startRecordingImmediately = true, rateBasedSampler = mockSampler ) { _, _, _, _ -> mockRecorder } @@ -1070,6 +1076,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + touchPrivacy = fakeConfiguration.touchPrivacy, startRecordingImmediately = false, rateBasedSampler = mockSampler ) { _, _, _, _ -> mockRecorder } @@ -1102,6 +1109,7 @@ internal class SessionReplayFeatureTest { customEndpointUrl = fakeConfiguration.customEndpointUrl, privacy = fakeConfiguration.privacy, imagePrivacy = fakeConfiguration.imagePrivacy, + touchPrivacy = fakeConfiguration.touchPrivacy, startRecordingImmediately = true, rateBasedSampler = mockSampler ) { _, _, _, _ -> mockRecorder } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt index a859b67591..632427d2d3 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt @@ -14,6 +14,7 @@ import android.view.Window import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.recorder.callback.NoOpWindowCallback @@ -69,6 +70,9 @@ internal class WindowCallbackInterceptorTest { @Forgery lateinit var fakeImagePrivacy: ImagePrivacy + @Forgery + lateinit var fakeTouchPrivacy: TouchPrivacy + private lateinit var fakeWindowsList: List private lateinit var mockActivity: Activity @@ -83,7 +87,8 @@ internal class WindowCallbackInterceptorTest { mockTimeProvider, mockInternalLogger, fakePrivacy, - fakeImagePrivacy + fakeImagePrivacy, + fakeTouchPrivacy ) } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt index 90c781d08d..cefe115b9e 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt @@ -15,6 +15,7 @@ import android.view.Window import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.async.TouchEventRecordedDataQueueItem @@ -103,9 +104,6 @@ internal class RecorderWindowCallbackTest { @Forgery lateinit var fakePrivacy: SessionReplayPrivacy - @Forgery - lateinit var fakeImagePrivacy: ImagePrivacy - @BeforeEach fun `set up`() { val mockResources = mock { @@ -124,7 +122,8 @@ internal class RecorderWindowCallbackTest { viewOnDrawInterceptor = mockViewOnDrawInterceptor, internalLogger = mockInternalLogger, privacy = fakePrivacy, - imagePrivacy = fakeImagePrivacy, + imagePrivacy = ImagePrivacy.MASK_NONE, + touchPrivacy = TouchPrivacy.SHOW, copyEvent = { it }, motionEventUtils = mockEventUtils, motionUpdateThresholdInNs = TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS, @@ -427,7 +426,7 @@ internal class RecorderWindowCallbackTest { // Then inOrder(mockViewOnDrawInterceptor) { verify(mockViewOnDrawInterceptor).stopIntercepting() - verify(mockViewOnDrawInterceptor).intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + verify(mockViewOnDrawInterceptor).intercept(fakeDecorViews, fakePrivacy, ImagePrivacy.MASK_NONE) } } @@ -446,6 +445,90 @@ internal class RecorderWindowCallbackTest { // endregion + // region touchPrivacy + + @Test + fun `M capture touch events W onTouchEvent { TouchPrivacy SHOW }`( + forge: Forge + ) { + // Given + val fakeEvent1Records = forge.touchRecords(MobileSegment.PointerEventType.DOWN) + val relatedMotionEvent1 = fakeEvent1Records.asMotionEvent() + val fakeEvent2Records = forge.touchRecords(MobileSegment.PointerEventType.MOVE) + val relatedMotionEvent2 = fakeEvent2Records.asMotionEvent() + val fakeEvent3Records = forge.touchRecords(MobileSegment.PointerEventType.UP) + val relatedMotionEvent3 = fakeEvent3Records.asMotionEvent() + + testedWindowCallback = RecorderWindowCallback( + appContext = mockContext, + recordedDataQueueHandler = mockRecordedDataQueueHandler, + wrappedCallback = mockWrappedCallback, + timeProvider = mockTimeProvider, + viewOnDrawInterceptor = mockViewOnDrawInterceptor, + internalLogger = mockInternalLogger, + privacy = fakePrivacy, + imagePrivacy = ImagePrivacy.MASK_NONE, + touchPrivacy = TouchPrivacy.SHOW, + copyEvent = { it }, + motionEventUtils = mockEventUtils, + motionUpdateThresholdInNs = TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS, + flushPositionBufferThresholdInNs = TEST_FLUSH_BUFFER_THRESHOLD_NS, + windowInspector = mockWindowInspector + ) + + // When + testedWindowCallback.dispatchTouchEvent(relatedMotionEvent1) + testedWindowCallback.dispatchTouchEvent(relatedMotionEvent2) + testedWindowCallback.dispatchTouchEvent(relatedMotionEvent3) + + // Then + val expectedRecords = fakeEvent1Records + fakeEvent2Records + fakeEvent3Records + verify(mockRecordedDataQueueHandler).addTouchEventItem(expectedRecords) + verify(mockRecordedDataQueueHandler).tryToConsumeItems() + assertThat(testedWindowCallback.pointerInteractions).isEmpty() + } + + @Test + fun `M not capture touch events W onTouchEvent { TouchPrivacy HIDE }`( + forge: Forge + ) { + // Given + val fakeEvent1Records = forge.touchRecords(MobileSegment.PointerEventType.DOWN) + val relatedMotionEvent1 = fakeEvent1Records.asMotionEvent() + val fakeEvent2Records = forge.touchRecords(MobileSegment.PointerEventType.MOVE) + val relatedMotionEvent2 = fakeEvent2Records.asMotionEvent() + val fakeEvent3Records = forge.touchRecords(MobileSegment.PointerEventType.UP) + val relatedMotionEvent3 = fakeEvent3Records.asMotionEvent() + + testedWindowCallback = RecorderWindowCallback( + appContext = mockContext, + recordedDataQueueHandler = mockRecordedDataQueueHandler, + wrappedCallback = mockWrappedCallback, + timeProvider = mockTimeProvider, + viewOnDrawInterceptor = mockViewOnDrawInterceptor, + internalLogger = mockInternalLogger, + privacy = fakePrivacy, + imagePrivacy = ImagePrivacy.MASK_NONE, + touchPrivacy = TouchPrivacy.HIDE, + copyEvent = { it }, + motionEventUtils = mockEventUtils, + motionUpdateThresholdInNs = TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS, + flushPositionBufferThresholdInNs = TEST_FLUSH_BUFFER_THRESHOLD_NS, + windowInspector = mockWindowInspector + ) + + // When + testedWindowCallback.dispatchTouchEvent(relatedMotionEvent1) + testedWindowCallback.dispatchTouchEvent(relatedMotionEvent2) + testedWindowCallback.dispatchTouchEvent(relatedMotionEvent3) + + // Then + verifyNoInteractions(mockRecordedDataQueueHandler) + assertThat(testedWindowCallback.pointerInteractions).isEmpty() + } + + // endregion + // region Internal private fun Forge.touchRecords( diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt index d81be43baa..29cca29982 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt @@ -179,7 +179,7 @@ internal class DefaultImageWireframeHelperTest { // region createImageWireframe @Test - fun `M return content placeholder W createImageWireframe() { ImagePrivacy NONE }`() { + fun `M return content placeholder W createImageWireframe() { ImagePrivacy MASK_ALL }`() { // When val wireframe = testedHelper.createImageWireframe( view = mockView, @@ -201,7 +201,7 @@ internal class DefaultImageWireframeHelperTest { } @Test - fun `M not return image wireframe W createImageWireframe() { ImagePrivacy ALL }`() { + fun `M return image wireframe W createImageWireframe() { ImagePrivacy MASK_NONE }`() { // When val wireframe = testedHelper.createImageWireframe( view = mockView, From f00ba95d13eed5697ab9c5c85a47cf351cf30798 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Mon, 2 Sep 2024 11:00:26 +0200 Subject: [PATCH 007/111] RUM-5919 Fix the flakiness in the KioskTrackingTest --- .../android/sdk/integration/rum/KioskTrackingTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/KioskTrackingTest.kt b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/KioskTrackingTest.kt index 9897266592..81e2b7eaca 100644 --- a/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/KioskTrackingTest.kt +++ b/instrumented/integration/src/androidTest/kotlin/com/datadog/android/sdk/integration/rum/KioskTrackingTest.kt @@ -17,7 +17,6 @@ import com.datadog.android.sdk.integration.R import com.datadog.android.sdk.rules.KioskTrackingActivityTestRule import com.datadog.android.sdk.rules.RumMockServerActivityTestRule import com.datadog.tools.unit.ConditionWatcher -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -37,9 +36,7 @@ internal class KioskTrackingTest : trackingConsent = TrackingConsent.GRANTED ) - // TODO RUM-5919: Fix and re - enable this flaky test @Test - @Ignore("Flaky test, needs to be fixed") fun verifyRumEvents() { val expectedEvents = runInstrumentationScenario(mockServerRule) @@ -114,8 +111,11 @@ internal class KioskTrackingTest : onView(withId(R.id.kiosk_button)).perform(click()) instrumentation.waitForIdleSync() + Thread.sleep(500) onView(withId(R.id.kiosk_back_button)).perform(click()) + Thread.sleep(500) instrumentation.waitForIdleSync() + Thread.sleep(4000) // give some time to settle and register the events // No events on this view - one for view stop view / stop session // ignore first view event, it will be reduced From b3b46f5dc1c217d4dee14285a367a992a51c8dbc Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Thu, 5 Sep 2024 13:36:51 +0200 Subject: [PATCH 008/111] RUM-6060 stop upload worker on upload failure --- .../data/upload/DataOkHttpUploader.kt | 12 +- .../core/internal/data/upload/UploadStatus.kt | 2 + .../core/internal/data/upload/UploadWorker.kt | 6 +- .../core/internal/metrics/RemovalReason.kt | 2 +- .../internal/data/upload/UploadWorkerTest.kt | 1132 ++++++++++------- 5 files changed, 685 insertions(+), 469 deletions(-) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/DataOkHttpUploader.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/DataOkHttpUploader.kt index 543f3222d6..fc1d367970 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/DataOkHttpUploader.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/DataOkHttpUploader.kt @@ -172,12 +172,16 @@ internal class DataOkHttpUploader( ): UploadStatus { return when (code) { HTTP_ACCEPTED -> UploadStatus.Success(code) - HTTP_BAD_REQUEST -> UploadStatus.HttpClientError(code) - HTTP_UNAUTHORIZED -> UploadStatus.InvalidTokenError(code) + + HTTP_UNAUTHORIZED, HTTP_FORBIDDEN -> UploadStatus.InvalidTokenError(code) - HTTP_CLIENT_TIMEOUT -> UploadStatus.HttpClientRateLimiting(code) - HTTP_ENTITY_TOO_LARGE -> UploadStatus.HttpClientError(code) + + HTTP_CLIENT_TIMEOUT, HTTP_TOO_MANY_REQUESTS -> UploadStatus.HttpClientRateLimiting(code) + + HTTP_BAD_REQUEST, + HTTP_ENTITY_TOO_LARGE -> UploadStatus.HttpClientError(code) + HTTP_INTERNAL_ERROR, HTTP_BAD_GATEWAY, HTTP_UNAVAILABLE, diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadStatus.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadStatus.kt index 44e2a8baed..ab439aed8e 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadStatus.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadStatus.kt @@ -15,6 +15,7 @@ internal sealed class UploadStatus( ) { internal class Success(responseCode: Int) : UploadStatus(shouldRetry = false, code = responseCode) + internal class NetworkError(throwable: Throwable) : UploadStatus(shouldRetry = true, throwable = throwable) internal class DNSError(throwable: Throwable) : UploadStatus(shouldRetry = true, throwable = throwable) internal class RequestCreationError(throwable: Throwable?) : @@ -26,6 +27,7 @@ internal sealed class UploadStatus( internal class HttpServerError(responseCode: Int) : UploadStatus(shouldRetry = true, code = responseCode) internal class HttpClientRateLimiting(responseCode: Int) : UploadStatus(shouldRetry = true, code = responseCode) internal class UnknownHttpError(responseCode: Int) : UploadStatus(shouldRetry = false, code = responseCode) + internal class UnknownException(throwable: Throwable) : UploadStatus(shouldRetry = true, throwable = throwable) internal object UnknownStatus : UploadStatus(shouldRetry = false, code = UNKNOWN_RESPONSE_CODE) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadWorker.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadWorker.kt index a5f74790c0..533df17251 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadWorker.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/UploadWorker.kt @@ -97,8 +97,10 @@ internal class UploadWorker( RemovalReason.IntakeCode(uploadStatus.code), deleteBatch = !uploadStatus.shouldRetry ) - @Suppress("UnsafeThirdPartyFunctionCall") // safe to add - taskQueue.offer(UploadNextBatchTask(taskQueue, sdkCore, feature)) + if (uploadStatus is UploadStatus.Success) { + @Suppress("UnsafeThirdPartyFunctionCall") // safe to add + taskQueue.offer(UploadNextBatchTask(taskQueue, sdkCore, feature)) + } } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/RemovalReason.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/RemovalReason.kt index a99ebe3603..8fae3aa693 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/RemovalReason.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/metrics/RemovalReason.kt @@ -11,7 +11,7 @@ internal sealed class RemovalReason { internal fun includeInMetrics(): Boolean { return this !is Flushed } - internal class IntakeCode(private val responseCode: Int) : RemovalReason() { + internal data class IntakeCode(private val responseCode: Int) : RemovalReason() { override fun toString(): String { return "intake-code-$responseCode" } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/UploadWorkerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/UploadWorkerTest.kt index 6ea2d8b36f..bfaa7ad131 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/UploadWorkerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/data/upload/UploadWorkerTest.kt @@ -12,12 +12,11 @@ import androidx.work.ListenableWorker import androidx.work.Worker import androidx.work.WorkerParameters import com.datadog.android.Datadog -import com.datadog.android.api.InternalLogger import com.datadog.android.api.context.DatadogContext -import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.RawBatchEvent import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.SdkFeature +import com.datadog.android.core.internal.data.upload.UploadStatus.Companion.UNKNOWN_RESPONSE_CODE import com.datadog.android.core.internal.metrics.RemovalReason import com.datadog.android.core.internal.persistence.BatchData import com.datadog.android.core.internal.persistence.BatchId @@ -25,12 +24,12 @@ import com.datadog.android.core.internal.persistence.Storage import com.datadog.android.utils.config.ApplicationContextTestConfiguration import com.datadog.android.utils.config.InternalLoggerTestConfiguration import com.datadog.android.utils.forge.Configurator -import com.datadog.android.utils.verifyLog import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -40,23 +39,17 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions -import org.junit.jupiter.api.fail -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.invocation.InvocationOnMock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings -import org.mockito.kotlin.argThat import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import org.mockito.stubbing.Answer -import java.util.concurrent.Executors @Extensions( ExtendWith(MockitoExtension::class), @@ -72,48 +65,42 @@ internal class UploadWorkerTest { @Mock lateinit var mockSdkCore: InternalSdkCore - @Mock - lateinit var mockFeatureA: SdkFeature + @Forgery + lateinit var fakeDatadogContext: DatadogContext - @Mock - lateinit var mockStorageA: Storage + @StringForgery + lateinit var fakeInstanceName: String - @Mock - lateinit var mockUploaderA: DataUploader + @Forgery + lateinit var fakeWorkerParameters: WorkerParameters - @Mock - lateinit var mockFeatureB: SdkFeature + var fakeFeaturesCount: Int = 0 - @Mock - lateinit var mockStorageB: Storage + lateinit var mockFeatures: List - @Mock - lateinit var mockUploaderB: DataUploader + lateinit var mockUploaders: List - @Forgery - lateinit var fakeWorkerParameters: WorkerParameters + lateinit var mockStorages: List - @StringForgery - lateinit var fakeInstanceName: String + lateinit var fakeFeatureBatches: List>> - @Forgery - lateinit var fakeContext: DatadogContext + lateinit var fakeFeatureBatchIds: List> + + lateinit var fakeFeatureBatchMetadata: List> @BeforeEach - fun `set up`() { - whenever(mockSdkCore.getDatadogContext()) doReturn fakeContext + fun `set up`(forge: Forge) { + whenever(mockSdkCore.getDatadogContext()) doReturn fakeDatadogContext Datadog.registry.register(fakeInstanceName, mockSdkCore) + val fakeData = Data.Builder() .putString(UploadWorker.DATADOG_INSTANCE_NAME, fakeInstanceName) .build() fakeWorkerParameters = fakeWorkerParameters.copyWith(fakeData) - stubFeatures( - mockSdkCore, - listOf(mockFeatureA, mockFeatureB), - listOf(mockStorageA, mockStorageB), - listOf(mockUploaderA, mockUploaderB) - ) + fakeFeaturesCount = forge.anInt(2, 8) + createFakeBatches(forge) + stubFeaturesStorage() testedWorker = UploadWorker( appContext.mockInstance, @@ -126,530 +113,746 @@ internal class UploadWorkerTest { Datadog.registry.clear() } + // region setup + + private fun createFakeBatches(forge: Forge) { + fakeFeatureBatches = List(fakeFeaturesCount) { + forge.aList { forge.aList { forge.getForgery() } } + } + + fakeFeatureBatchIds = List(fakeFeaturesCount) { featureIndex -> + forge.aList(fakeFeatureBatches[featureIndex].size) { BatchId(forge.aString()) } + } + + fakeFeatureBatchMetadata = List(fakeFeaturesCount) { featureIndex -> + forge.aList(fakeFeatureBatches[featureIndex].size) { forge.aNullable { aString().toByteArray() } } + } + } + + private fun stubFeaturesStorage() { + mockFeatures = List(fakeFeaturesCount) { mock() } + mockUploaders = List(fakeFeaturesCount) { mock() } + mockStorages = List(fakeFeaturesCount) { mock() } + + whenever(mockSdkCore.getAllFeatures()) doReturn mockFeatures + + mockFeatures.forEachIndexed { featureIndex, feature -> + whenever(feature.uploader) doReturn mockUploaders[featureIndex] + whenever(feature.storage) doReturn mockStorages[featureIndex] + + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + val fakeBatchMetadata = fakeFeatureBatchMetadata[featureIndex] + + val batchesCount = fakeBatches.size + whenever(mockStorages[featureIndex].readNextBatch()) + .thenAnswer(object : Answer { + var invocationCount: Int = 0 + + override fun answer(invocation: InvocationOnMock): BatchData? { + if (invocationCount >= batchesCount) { + return null + } + val fakeBatch = fakeBatches[invocationCount] + val fakeBatchId = fakeBatchIds[invocationCount] + val fakeMetadata = fakeBatchMetadata[invocationCount] + invocationCount++ + + return BatchData( + id = fakeBatchId, + data = fakeBatch, + metadata = fakeMetadata + ) + } + }) + } + } + + private fun stubFeaturesUploaders( + successStatusCode: Int = 202, + successfulUntilIdx: Int = Int.MAX_VALUE, + secondaryStatus: UploadStatus = UploadStatus.UnknownStatus + ) { + mockFeatures.forEachIndexed { featureIndex, _ -> + val mockUploader = mockUploaders[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeBatchMetadata = fakeFeatureBatchMetadata[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, batch -> + val status = if (batchIndex < successfulUntilIdx) { + UploadStatus.Success(successStatusCode) + } else { + secondaryStatus + } + whenever( + mockUploader.upload( + fakeDatadogContext, + batch, + fakeBatchMetadata[batchIndex] + ) + ) doReturn status + } + } + } + + // endregion + // region doWork @Test - fun `M send batches W doWork() {single batch per feature}`( - @Forgery batchA: List, - @StringForgery batchAMeta: String, - @Forgery batchB: List, - @StringForgery batchBMeta: String, - forge: Forge + fun `M send all batches W doWork() {all success}`( + @IntForgery(200, 300) successStatusCode: Int ) { // Given - val batchAMetadata = forge.aNullable { batchAMeta.toByteArray() } - val batchBMetadata = forge.aNullable { batchBMeta.toByteArray() } - - val batchId1 = mock() - val batchId2 = mock() - stubReadSequence( - mockStorageA, - batchId1, - batchA, - batchAMetadata - ) + stubFeaturesUploaders(successStatusCode) - stubReadSequence( - mockStorageB, - batchId2, - batchB, - batchBMetadata - ) + // When + val result = testedWorker.doWork() - val uploadStatus1 = forge.getForgery(UploadStatus.Success::class.java) - val uploadStatus2 = forge.getForgery(UploadStatus.Success::class.java) - whenever( - mockUploaderA.upload( - fakeContext, - batchA, - batchAMetadata - ) - ) doReturn uploadStatus1 - whenever( - mockUploaderB.upload( - fakeContext, - batchB, - batchBMetadata - ) - ) doReturn uploadStatus2 + // Then + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } + } + } + + @Test + fun `M send all batches until failure W doWork() {unauthorized}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @IntForgery(400, 499) failureStatusCode: Int + + ) { + // Given + val failingStatus = UploadStatus.InvalidTokenError(failureStatusCode) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) // When val result = testedWorker.doWork() // Then - verify(mockUploaderA).upload( - fakeContext, - batchA, - batchAMetadata - ) - verify(mockUploaderB).upload( - fakeContext, - batchB, - batchBMetadata - ) + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - verify(mockStorageA).confirmBatchRead( - eq(batchId1), - argThat { this.toString() == "intake-code-${uploadStatus1.code}" }, - eq(true) - ) - verify(mockStorageB).confirmBatchRead( - eq(batchId2), - argThat { this.toString() == "intake-code-${uploadStatus2.code}" }, - eq(true) - ) + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(failureStatusCode), + deleteBatch = true + ) + } + } - assertThat(result) - .isEqualTo(ListenableWorker.Result.success()) + verifyNoMoreInteractions(mockUploader) + } } - @ParameterizedTest - @MethodSource("errorStatusValues") - fun `M send and keep batches W doWork() {single batch per feature with error}`( - status: UploadStatus, - @Forgery batchA: List, - @StringForgery batchAMeta: String, - @Forgery batchB: List, - @StringForgery batchBMeta: String, - forge: Forge + @Test + fun `M send all batches until failure W doWork() {rate limiting}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @IntForgery(400, 499) failureStatusCode: Int ) { // Given - val batchAMetadata = forge.aNullable { batchAMeta.toByteArray() } - val batchBMetadata = forge.aNullable { batchBMeta.toByteArray() } - - val batchId1 = mock() - stubReadSequence( - mockStorageA, - batchId1, - batchA, - batchAMetadata - ) - - val batchId2 = mock() - stubReadSequence( - mockStorageB, - batchId2, - batchB, - batchBMetadata - ) - - whenever( - mockUploaderA.upload( - fakeContext, - batchA, - batchAMetadata - ) - ) doReturn status - whenever( - mockUploaderB.upload( - fakeContext, - batchB, - batchBMetadata - ) - ) doReturn status + val failingStatus = UploadStatus.HttpClientRateLimiting(failureStatusCode) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) // When val result = testedWorker.doWork() // Then - verify(mockUploaderA).upload( - fakeContext, - batchA, - batchAMetadata - ) - verify(mockUploaderB).upload( - fakeContext, - batchB, - batchBMetadata - ) - verify(mockStorageA).confirmBatchRead( - eq(batchId1), - argThat { this.toString() == "intake-code-${status.code}" }, - eq(!status.shouldRetry) - ) - verify(mockStorageB).confirmBatchRead( - eq(batchId2), - argThat { this.toString() == "intake-code-${status.code}" }, - eq(!status.shouldRetry) - ) + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } + + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(failureStatusCode), + deleteBatch = false + ) + } + } - assertThat(result) - .isEqualTo(ListenableWorker.Result.success()) + verifyNoMoreInteractions(mockUploader) + } } @Test - fun `M send batches W doWork() {multiple batches, all Success}`(forge: Forge) { + fun `M send all batches until failure W doWork() {client error}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @IntForgery(400, 499) failureStatusCode: Int + ) { // Given - val batchesA = forge.aList { - aList { RawBatchEvent(aString().toByteArray()) } - } - val batchesAMeta = forge.aList(batchesA.size) { - forge.aNullable { forge.aString().toByteArray() } - } - val aIds = forge.aList(batchesA.size) { mock() } + val failingStatus = UploadStatus.HttpClientError(failureStatusCode) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) - val batchB = forge.aList { RawBatchEvent(aString().toByteArray()) } - val batchBMeta = forge.aString().toByteArray() + // When + val result = testedWorker.doWork() - stubMultipleReadSequence( - mockStorageA, - aIds, - batchesA, - batchesAMeta - ) + // Then + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - val batchId = mock() - stubReadSequence( - mockStorageB, - batchId, - batchB, - batchBMeta - ) + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(failureStatusCode), + deleteBatch = true + ) + } + } - val aStatuses = batchesA.map { forge.getForgery(UploadStatus.Success::class.java) } - batchesA.forEachIndexed { index, batch -> - whenever( - mockUploaderA.upload( - fakeContext, - batch, - batchesAMeta[index] - ) - ) doReturn aStatuses[index] + verifyNoMoreInteractions(mockUploader) } + } - val successStatus = forge.getForgery(UploadStatus.Success::class.java) - whenever( - mockUploaderB.upload( - fakeContext, - batchB, - batchBMeta - ) - ) doReturn successStatus + @Test + fun `M send all batches until failure W doWork() {server error}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @IntForgery(400, 499) failureStatusCode: Int + ) { + // Given + val failingStatus = UploadStatus.HttpServerError(failureStatusCode) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) // When val result = testedWorker.doWork() // Then - batchesA.forEachIndexed { index, batch -> - verify(mockUploaderA).upload( - fakeContext, - batch, - batchesAMeta[index] - ) - verify(mockStorageA).confirmBatchRead( - eq(aIds[index]), - argThat { this.toString() == "intake-code-${aStatuses[index].code}" }, - eq(true) - ) - } + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - verify(mockUploaderB).upload( - fakeContext, - batchB, - batchBMeta - ) - verify(mockStorageB).confirmBatchRead( - eq(batchId), - argThat { this.toString() == "intake-code-${successStatus.code}" }, - eq(true) - ) - assertThat(result) - .isEqualTo(ListenableWorker.Result.success()) + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(failureStatusCode), + deleteBatch = false + ) + } + } + + verifyNoMoreInteractions(mockUploader) + } } @Test - fun `M send batches W doWork() {multiple batches, all Success, async storage}`(forge: Forge) { + fun `M send all batches until failure W doWork() {redirection}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @IntForgery(300, 399) failureStatusCode: Int + ) { // Given - val batchesA = forge.aList { - aList { RawBatchEvent(aString().toByteArray()) } - } - val batchesAMeta = forge.aList(batchesA.size) { - aNullable { aString().toByteArray() } - } - val aIds = forge.aList(batchesA.size) { mock() } - - val batchB = forge.aList { RawBatchEvent(aString().toByteArray()) } - val batchBMeta = forge.aString().toByteArray() - val aStatuses = batchesA.map { forge.getForgery(UploadStatus.Success::class.java) } + val failingStatus = UploadStatus.HttpRedirection(failureStatusCode) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) - stubMultipleReadSequence( - mockStorageA, - aIds, - batchesA, - batchesAMeta - ) + // When + val result = testedWorker.doWork() - val batchId = mock() - stubReadSequence( - mockStorageB, - batchId, - batchB, - batchBMeta - ) + // Then + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - val executorService = Executors.newSingleThreadExecutor() - whenever(mockFeatureA.storage) doReturn StorageDelegate(mockStorageA) - whenever(mockFeatureB.storage) doReturn StorageDelegate(mockStorageB) + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(failureStatusCode), + deleteBatch = true + ) + } + } - batchesA.forEachIndexed { index, batch -> - whenever( - mockUploaderA.upload( - fakeContext, - batch, - batchesAMeta[index] - ) - ) doReturn aStatuses[index] + verifyNoMoreInteractions(mockUploader) } + } - val successStatus = forge.getForgery(UploadStatus.Success::class.java) - whenever( - mockUploaderB.upload( - fakeContext, - batchB, - batchBMeta - ) - ) doReturn successStatus + @Test + fun `M send all batches until failure W doWork() {unknown http error}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @IntForgery(400, 499) failureStatusCode: Int + ) { + // Given + val failingStatus = UploadStatus.UnknownHttpError(failureStatusCode) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) // When val result = testedWorker.doWork() // Then - batchesA.forEachIndexed { index, batch -> - verify(mockUploaderA).upload( - fakeContext, - batch, - batchesAMeta[index] - ) - verify(mockStorageA).confirmBatchRead( - eq(aIds[index]), - argThat { this.toString() == "intake-code-${aStatuses[index].code}" }, - eq(true) - ) - } - - verify(mockUploaderB).upload( - fakeContext, - batchB, - batchBMeta - ) - verify(mockStorageB).confirmBatchRead( - eq(batchId), - argThat { this.toString() == "intake-code-${successStatus.code}" }, - eq(true) - ) + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - assertThat(result) - .isEqualTo(ListenableWorker.Result.success()) + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(failureStatusCode), + deleteBatch = true + ) + } + } - executorService.shutdown() + verifyNoMoreInteractions(mockUploader) + } } - @ParameterizedTest - @MethodSource("errorStatusValues") - fun `M send batches W doWork() {multiple batches, some fails with retry}`( - failingStatus: UploadStatus, - forge: Forge + @Test + fun `M send all batches until failure W doWork() {network error}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @Forgery throwable: Exception ) { // Given - val batchesA = forge.aList { - aList { RawBatchEvent(aString().toByteArray()) } - } - val batchesAMeta = forge.aList(batchesA.size) { - aNullable { aString().toByteArray() } - } - val aIds = forge.aList(batchesA.size) { mock() } + val failingStatus = UploadStatus.NetworkError(throwable) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) - val batchB = forge.aList { RawBatchEvent(aString().toByteArray()) } - val batchBMeta = forge.aString().toByteArray() - - val failingBatchIndex = forge.anInt(min = 0, max = batchesA.size) - val aStatuses = List(batchesA.size) { index -> - if (index == failingBatchIndex) { - failingStatus - } else { - forge.getForgery(UploadStatus.Success::class.java) - } - } + // When + val result = testedWorker.doWork() - stubMultipleReadSequence( - mockStorageA, - aIds, - batchesA, - batchesAMeta - ) + // Then + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - val batchId = mock() - stubReadSequence( - mockStorageB, - batchId, - batchB, - batchBMeta - ) + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(UNKNOWN_RESPONSE_CODE), + deleteBatch = false + ) + } + } - val fakeUploadSuccess2 = forge.getForgery(UploadStatus.Success::class.java) - batchesA.forEachIndexed { index, batch -> - whenever( - mockUploaderA.upload( - fakeContext, - batch, - batchesAMeta[index] - ) - ) doReturn aStatuses[index] + verifyNoMoreInteractions(mockUploader) } + } - whenever( - mockUploaderB.upload( - fakeContext, - batchB, - batchBMeta - ) - ) doReturn fakeUploadSuccess2 + @Test + fun `M send all batches until failure W doWork() {dns error}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @Forgery throwable: Exception + ) { + // Given + val failingStatus = UploadStatus.DNSError(throwable) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) // When val result = testedWorker.doWork() // Then - batchesA.forEachIndexed { index, batch -> - verify(mockUploaderA).upload( - fakeContext, - batch, - batchesAMeta[index] - ) + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - if (index == failingBatchIndex) { - verify(mockStorageA).confirmBatchRead( - eq(aIds[index]), - argThat { this.toString() == "intake-code-${aStatuses[index].code}" }, - eq(!failingStatus.shouldRetry) - ) - } else { - verify(mockStorageA).confirmBatchRead( - eq(aIds[index]), - argThat { this.toString() == "intake-code-${aStatuses[index].code}" }, - eq(true) - ) + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(UNKNOWN_RESPONSE_CODE), + deleteBatch = false + ) + } } - } - verify(mockUploaderB).upload( - fakeContext, - batchB, - batchBMeta - ) - verify(mockStorageB).confirmBatchRead( - eq(batchId), - argThat { this.toString() == "intake-code-${fakeUploadSuccess2.code}" }, - eq(true) - ) - assertThat(result) - .isEqualTo(ListenableWorker.Result.success()) + verifyNoMoreInteractions(mockUploader) + } } @Test - fun `M log error W doWork() { SDK is not initialized }`() { + fun `M send all batches until failure W doWork() {request creation error}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @Forgery throwable: Exception + ) { // Given - Datadog.registry.clear() + val failingStatus = UploadStatus.RequestCreationError(throwable) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) // When val result = testedWorker.doWork() // Then - logger.mockInternalLogger.verifyLog( - InternalLogger.Level.ERROR, - InternalLogger.Target.USER, - UploadWorker.MESSAGE_NOT_INITIALIZED - ) - verifyNoInteractions(mockFeatureA, mockUploaderA) - verifyNoInteractions(mockFeatureB, mockUploaderB) - - assertThat(result) - .isEqualTo(ListenableWorker.Result.success()) - } - - // endregion + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - // region private + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(UNKNOWN_RESPONSE_CODE), + deleteBatch = true + ) + } + } - private fun stubFeatures( - core: InternalSdkCore, - features: List, - storages: List, - uploaders: List - ) { - whenever(core.getAllFeatures()) doReturn features - features.forEachIndexed { index, feature -> - whenever(feature.uploader) doReturn uploaders[index] - whenever(feature.storage) doReturn storages[index] + verifyNoMoreInteractions(mockUploader) } } - private fun stubReadSequence( - storage: Storage, - batchId: BatchId, - batch: List, - batchMetadata: ByteArray? + @Test + fun `M send all batches until failure W doWork() {unknown exception}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int, + @Forgery throwable: Exception ) { - stubMultipleReadSequence( - storage, - listOf(batchId), - listOf(batch), - listOf(batchMetadata) - ) - } + // Given + val failingStatus = UploadStatus.UnknownException(throwable) + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) - private fun stubMultipleReadSequence( - storage: Storage, - batchIds: List, - batches: List>, - batchMetadata: List - ) { - whenever(storage.readNextBatch()) - .thenAnswer(object : Answer { - var invocationCount: Int = 0 + // When + val result = testedWorker.doWork() - override fun answer(invocation: InvocationOnMock): BatchData? { - if (invocationCount >= batches.size) { - return null - } + // Then + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } - val batchId = batchIds[invocationCount] - return BatchData( - id = batchId, - data = batches[invocationCount], - metadata = batchMetadata[invocationCount++] + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(UNKNOWN_RESPONSE_CODE), + deleteBatch = false ) } - }) - } + } - private class StorageDelegate( - private val delegate: Storage - ) : Storage { - override fun writeCurrentBatch( - datadogContext: DatadogContext, - forceNewBatch: Boolean, - callback: (EventBatchWriter) -> Unit - ) { - fail("we don't expect this one to be called") + verifyNoMoreInteractions(mockUploader) } + } - override fun readNextBatch(): BatchData? { - return delegate.readNextBatch() - } + @Test + fun `M send all batches until failure W doWork() {unknown status}`( + @IntForgery(200, 300) successStatusCode: Int, + @IntForgery(0, 8) successfulBatchCount: Int + ) { + // Given + val failingStatus = UploadStatus.UnknownStatus + stubFeaturesUploaders(successStatusCode, successfulBatchCount, failingStatus) - override fun confirmBatchRead( - batchId: BatchId, - removalReason: RemovalReason, - deleteBatch: Boolean - ) { - delegate.confirmBatchRead(batchId, removalReason, deleteBatch) - } + // When + val result = testedWorker.doWork() + + // Then + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + repeat(fakeFeaturesCount) { featureIndex -> + val mockUploader = mockUploaders[featureIndex] + val mockStorage = mockStorages[featureIndex] + val fakeBatches = fakeFeatureBatches[featureIndex] + val fakeMetadata = fakeFeatureBatchMetadata[featureIndex] + val fakeBatchIds = fakeFeatureBatchIds[featureIndex] + + fakeBatches.forEachIndexed { batchIndex, fakeBatch -> + // n successful batches + if (batchIndex < successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(successStatusCode), + deleteBatch = true + ) + } + + // failing batch + if (batchIndex == successfulBatchCount) { + verify(mockUploader).upload( + context = fakeDatadogContext, + batch = fakeBatch, + batchMeta = fakeMetadata[batchIndex] + ) + verify(mockStorage).confirmBatchRead( + batchId = fakeBatchIds[batchIndex], + removalReason = RemovalReason.IntakeCode(UNKNOWN_RESPONSE_CODE), + deleteBatch = true + ) + } + } - override fun dropAll() { - fail("we don't expect this one to be called") + verifyNoMoreInteractions(mockUploader) } } + // endregion + + // region Internal + private fun WorkerParameters.copyWith( inputData: Data ): WorkerParameters { @@ -681,21 +884,26 @@ internal class UploadWorkerTest { } @JvmStatic - fun errorStatusValues(): List { + fun errorWithRetryStatusValues(): List { val forge = Forge().apply { Configurator().configure(this) } return listOf( - forge.getForgery(UploadStatus.HttpClientError::class.java), + forge.getForgery(UploadStatus.Success::class.java), + + forge.getForgery(UploadStatus.InvalidTokenError::class.java), forge.getForgery(UploadStatus.HttpClientRateLimiting::class.java), - forge.getForgery(UploadStatus.HttpRedirection::class.java), + forge.getForgery(UploadStatus.HttpClientError::class.java), forge.getForgery(UploadStatus.HttpServerError::class.java), - forge.getForgery(UploadStatus.InvalidTokenError::class.java), + forge.getForgery(UploadStatus.HttpRedirection::class.java), + forge.getForgery(UploadStatus.UnknownHttpError::class.java), + forge.getForgery(UploadStatus.NetworkError::class.java), + forge.getForgery(UploadStatus.DNSError::class.java), forge.getForgery(UploadStatus.RequestCreationError::class.java), + forge.getForgery(UploadStatus.UnknownException::class.java), - forge.getForgery(UploadStatus.UnknownHttpError::class.java), forge.getForgery(UploadStatus.UnknownStatus::class.java) ) } From a8ce83aae807c600ac898f38587dbc58d1d80329 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:19:25 +0300 Subject: [PATCH 009/111] RUM-5761: Add TextAndInputPrivacy --- .../internal/TelemetryEventHandler.kt | 16 +++- .../internal/SliderWireframeMapper.kt | 4 +- .../material/SliderWireframeMapperTest.kt | 8 +- .../forge/MappingContextForgeryFactory.kt | 2 +- .../api/apiSurface | 13 ++- .../api/dd-sdk-android-session-replay.api | 29 ++++-- .../android/sessionreplay/SessionReplay.kt | 1 + .../SessionReplayConfiguration.kt | 53 ++++++++++- .../sessionreplay/TextAndInputPrivacy.kt | 32 +++++++ .../internal/DefaultRecorderProvider.kt | 6 +- .../internal/SessionReplayFeature.kt | 18 +++- .../recorder/DefaultOnDrawListenerProducer.kt | 6 +- .../recorder/OnDrawListenerProducer.kt | 4 +- .../recorder/SessionReplayRecorder.kt | 22 ++--- .../internal/recorder/SnapshotProducer.kt | 6 +- .../recorder/ViewOnDrawInterceptor.kt | 7 +- .../recorder/WindowCallbackInterceptor.kt | 8 +- .../callback/RecorderWindowCallback.kt | 6 +- .../listener/WindowsOnDrawListener.kt | 6 +- .../mapper/CheckableWireframeMapper.kt | 4 +- .../recorder/mapper/NumberPickerMapper.kt | 8 +- .../mapper/ProgressBarWireframeMapper.kt | 10 +- .../recorder/mapper/SeekBarWireframeMapper.kt | 4 +- .../sessionreplay/recorder/MappingContext.kt | 6 +- .../recorder/mapper/EditTextMapper.kt | 26 +++--- .../recorder/mapper/TextViewMapper.kt | 20 ++-- .../SessionReplayConfigurationBuilderTest.kt | 85 +++++++++++++---- .../SessionReplayRecorderTest.kt | 18 ++-- .../forge/MappingContextForgeryFactory.kt | 2 +- ...essionReplayConfigurationForgeryFactory.kt | 2 + .../internal/SessionReplayFeatureTest.kt | 12 +++ .../internal/recorder/SnapshotProducerTest.kt | 24 ++--- .../recorder/ViewOnDrawInterceptorTest.kt | 34 +++---- .../recorder/WindowCallbackInterceptorTest.kt | 18 ++-- .../callback/RecorderWindowCallbackTest.kt | 12 +-- .../listener/WindowsOnDrawListenerTest.kt | 12 +-- .../mapper/BaseCheckableTextViewMapperTest.kt | 11 ++- .../mapper/BaseSwitchCompatMapperTest.kt | 8 +- .../recorder/mapper/ButtonMapperTest.kt | 6 +- .../recorder/mapper/NumberPickerMapperTest.kt | 42 ++++----- .../mapper/ProgressBarWireframeMapperTest.kt | 24 ++--- .../mapper/SeekBarWireframeMapperTest.kt | 26 +++--- .../recorder/mapper/SwitchCompatMapperTest.kt | 14 ++- .../mapper/AbstractWireframeMapperTest.kt | 6 +- .../recorder/mapper/ButtonMapperAllowTest.kt | 4 +- .../mapper/ButtonMapperMaskInputTest.kt | 4 +- .../recorder/mapper/ButtonMapperMaskTest.kt | 4 +- .../mapper/EditTextMapperAllowTest.kt | 4 +- .../mapper/EditTextMapperMaskInputTest.kt | 4 +- .../recorder/mapper/EditTextMapperMaskTest.kt | 4 +- .../recorder/mapper/EditTextMapperTest.kt | 6 +- .../mapper/TextViewMapperAllowTest.kt | 4 +- .../mapper/TextViewMapperMaskInputTest.kt | 4 +- .../recorder/mapper/TextViewMapperMaskTest.kt | 4 +- .../recorder/mapper/TextViewMapperTest.kt | 6 +- .../android/webview/WebViewTracking.kt | 51 ++++++++++- .../android/webview/WebViewTrackingTest.kt | 91 +++++++++++++++++-- .../BaseSessionReplayActivity.kt | 1 + .../sample/benchmark/DatadogBenchmark.kt | 1 + .../android/sample/SampleApplication.kt | 1 + 60 files changed, 604 insertions(+), 270 deletions(-) create mode 100644 features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TextAndInputPrivacy.kt diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 7e9a6a84d9..85c59a0309 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -252,8 +252,14 @@ internal class TelemetryEventHandler( as? Long val startRecordingImmediately = sessionReplayFeatureContext[SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY] as? Boolean - val sessionReplayPrivacy = sessionReplayFeatureContext[SESSION_REPLAY_PRIVACY_KEY] + val legacySessionReplayPrivacy = sessionReplayFeatureContext[SESSION_REPLAY_PRIVACY_KEY] as? String + val sessionReplayImagePrivacy = + sessionReplayFeatureContext[SESSION_REPLAY_IMAGE_PRIVACY_KEY] as? String + val sessionReplayTouchPrivacy = + sessionReplayFeatureContext[SESSION_REPLAY_TOUCH_PRIVACY_KEY] as? String + val sessionReplayTextAndInputPrivacy = + sessionReplayFeatureContext[SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY] as? String val rumConfig = sdkCore.getFeature(Feature.RUM_FEATURE_NAME) ?.unwrap() ?.configuration @@ -314,7 +320,10 @@ internal class TelemetryEventHandler( tracerApiVersion = openTelemetryApiVersion, trackNetworkRequests = trackNetworkRequests, sessionReplaySampleRate = sessionReplaySampleRate, - defaultPrivacyLevel = sessionReplayPrivacy, + defaultPrivacyLevel = legacySessionReplayPrivacy, + imagePrivacyLevel = sessionReplayImagePrivacy, + touchPrivacyLevel = sessionReplayTouchPrivacy, + textAndInputPrivacyLevel = sessionReplayTextAndInputPrivacy, startRecordingImmediately = startRecordingImmediately, batchProcessingLevel = coreConfiguration.batchProcessingLevel.toLong() ) @@ -394,6 +403,9 @@ internal class TelemetryEventHandler( internal const val OPENTELEMETRY_API_VERSION_CONTEXT_KEY = "opentelemetry_api_version" internal const val SESSION_REPLAY_SAMPLE_RATE_KEY = "session_replay_sample_rate" internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" + internal const val SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY = "session_replay_text_and_input_privacy" + internal const val SESSION_REPLAY_IMAGE_PRIVACY_KEY = "session_replay_image_privacy" + internal const val SESSION_REPLAY_TOUCH_PRIVACY_KEY = "session_replay_touch_privacy" internal const val SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY = "session_replay_start_immediate_recording" } diff --git a/features/dd-sdk-android-session-replay-material/src/main/kotlin/com/datadog/android/sessionreplay/material/internal/SliderWireframeMapper.kt b/features/dd-sdk-android-session-replay-material/src/main/kotlin/com/datadog/android/sessionreplay/material/internal/SliderWireframeMapper.kt index 847fbc7ffd..4be50ba2d6 100644 --- a/features/dd-sdk-android-session-replay-material/src/main/kotlin/com/datadog/android/sessionreplay/material/internal/SliderWireframeMapper.kt +++ b/features/dd-sdk-android-session-replay-material/src/main/kotlin/com/datadog/android/sessionreplay/material/internal/SliderWireframeMapper.kt @@ -9,7 +9,7 @@ package com.datadog.android.sessionreplay.material.internal import android.content.res.ColorStateList import androidx.annotation.UiThread import com.datadog.android.api.InternalLogger -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.recorder.MappingContext import com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper @@ -122,7 +122,7 @@ internal open class SliderWireframeMapper( ) ) - return if (mappingContext.privacy == SessionReplayPrivacy.ALLOW) { + return if (mappingContext.textAndInputPrivacy == TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) { listOf(trackNonActiveWireframe, trackActiveWireframe, thumbWireframe) } else { listOf(trackNonActiveWireframe) diff --git a/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/SliderWireframeMapperTest.kt b/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/SliderWireframeMapperTest.kt index d4596dd23a..d6c34f4bdd 100644 --- a/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/SliderWireframeMapperTest.kt +++ b/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/SliderWireframeMapperTest.kt @@ -6,7 +6,7 @@ package com.datadog.android.sessionreplay.material -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.material.forge.ForgeConfigurator import com.datadog.android.sessionreplay.material.internal.SliderWireframeMapper import com.datadog.android.sessionreplay.model.MobileSegment @@ -35,7 +35,7 @@ internal class SliderWireframeMapperTest : BaseSliderWireframeMapperTest() { @Test fun `M map the Slider to a list of wireframes W map() {ALLOW}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) val expectedInactiveTrackWireframe = MobileSegment.Wireframe.ShapeWireframe( id = fakeInactiveTrackId, x = fakeExpectedInactiveTrackXPos, @@ -92,7 +92,7 @@ internal class SliderWireframeMapperTest : BaseSliderWireframeMapperTest() { @Test fun `M map the Slider to a list of wireframes W map() {MASK}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL) val expectedInactiveTrackWireframe = MobileSegment.Wireframe.ShapeWireframe( id = fakeInactiveTrackId, x = fakeExpectedInactiveTrackXPos, @@ -124,7 +124,7 @@ internal class SliderWireframeMapperTest : BaseSliderWireframeMapperTest() { @Test fun `M map the Slider to a list of wireframes W map() {MASK_USER_INPUT}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK_USER_INPUT) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS) val expectedInactiveTrackWireframe = MobileSegment.Wireframe.ShapeWireframe( id = fakeInactiveTrackId, x = fakeExpectedInactiveTrackXPos, diff --git a/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/forge/MappingContextForgeryFactory.kt b/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/forge/MappingContextForgeryFactory.kt index e103acd79f..b92a0dc098 100644 --- a/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/forge/MappingContextForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay-material/src/test/kotlin/com/datadog/android/sessionreplay/material/forge/MappingContextForgeryFactory.kt @@ -16,7 +16,7 @@ internal class MappingContextForgeryFactory : ForgeryFactory { return MappingContext( systemInformation = forge.getForgery(), imageWireframeHelper = mock(), - privacy = forge.getForgery(), + textAndInputPrivacy = forge.getForgery(), imagePrivacy = forge.getForgery(), hasOptionSelectorParent = forge.aBool() ) diff --git a/features/dd-sdk-android-session-replay/api/apiSurface b/features/dd-sdk-android-session-replay/api/apiSurface index 469bc98cc0..dae425c488 100644 --- a/features/dd-sdk-android-session-replay/api/apiSurface +++ b/features/dd-sdk-android-session-replay/api/apiSurface @@ -18,20 +18,25 @@ data class com.datadog.android.sessionreplay.SessionReplayConfiguration constructor(Float) fun addExtensionSupport(ExtensionSupport): Builder fun useCustomEndpoint(String): Builder - fun setPrivacy(SessionReplayPrivacy): Builder + DEPRECATED fun setPrivacy(SessionReplayPrivacy): Builder fun setImagePrivacy(ImagePrivacy): Builder fun setTouchPrivacy(TouchPrivacy): Builder fun startRecordingImmediately(Boolean): Builder + fun setTextAndInputPrivacy(TextAndInputPrivacy): Builder fun build(): SessionReplayConfiguration enum com.datadog.android.sessionreplay.SessionReplayPrivacy - ALLOW - MASK - MASK_USER_INPUT +enum com.datadog.android.sessionreplay.TextAndInputPrivacy + - MASK_SENSITIVE_INPUTS + - MASK_ALL_INPUTS + - MASK_ALL enum com.datadog.android.sessionreplay.TouchPrivacy - SHOW - HIDE data class com.datadog.android.sessionreplay.recorder.MappingContext - constructor(SystemInformation, com.datadog.android.sessionreplay.utils.ImageWireframeHelper, com.datadog.android.sessionreplay.SessionReplayPrivacy, com.datadog.android.sessionreplay.ImagePrivacy, Boolean = false) + constructor(SystemInformation, com.datadog.android.sessionreplay.utils.ImageWireframeHelper, com.datadog.android.sessionreplay.TextAndInputPrivacy, com.datadog.android.sessionreplay.ImagePrivacy, Boolean = false) interface com.datadog.android.sessionreplay.recorder.OptionSelectorDetector fun isOptionSelector(android.view.ViewGroup): Boolean data class com.datadog.android.sessionreplay.recorder.SystemInformation @@ -47,12 +52,12 @@ abstract class com.datadog.android.sessionreplay.recorder.mapper.BaseWireframeMa protected fun resolveShapeStyle(android.graphics.drawable.Drawable, Float, com.datadog.android.api.InternalLogger): com.datadog.android.sessionreplay.model.MobileSegment.ShapeStyle? class com.datadog.android.sessionreplay.recorder.mapper.EditTextMapper : TextViewMapper constructor(com.datadog.android.sessionreplay.utils.ViewIdentifierResolver, com.datadog.android.sessionreplay.utils.ColorStringFormatter, com.datadog.android.sessionreplay.utils.ViewBoundsResolver, com.datadog.android.sessionreplay.utils.DrawableToColorMapper) - override fun resolveCapturedText(android.widget.EditText, com.datadog.android.sessionreplay.SessionReplayPrivacy, Boolean): String + override fun resolveCapturedText(android.widget.EditText, com.datadog.android.sessionreplay.TextAndInputPrivacy, Boolean): String companion object open class com.datadog.android.sessionreplay.recorder.mapper.TextViewMapper : BaseAsyncBackgroundWireframeMapper constructor(com.datadog.android.sessionreplay.utils.ViewIdentifierResolver, com.datadog.android.sessionreplay.utils.ColorStringFormatter, com.datadog.android.sessionreplay.utils.ViewBoundsResolver, com.datadog.android.sessionreplay.utils.DrawableToColorMapper) override fun map(T, com.datadog.android.sessionreplay.recorder.MappingContext, com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback, com.datadog.android.api.InternalLogger): List - protected open fun resolveCapturedText(T, com.datadog.android.sessionreplay.SessionReplayPrivacy, Boolean): String + protected open fun resolveCapturedText(T, com.datadog.android.sessionreplay.TextAndInputPrivacy, Boolean): String interface com.datadog.android.sessionreplay.recorder.mapper.TraverseAllChildrenMapper : WireframeMapper interface com.datadog.android.sessionreplay.recorder.mapper.WireframeMapper fun map(T, com.datadog.android.sessionreplay.recorder.MappingContext, com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback, com.datadog.android.api.InternalLogger): List diff --git a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api index 3869e0667e..5746219517 100644 --- a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api +++ b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api @@ -34,8 +34,8 @@ public final class com/datadog/android/sessionreplay/SessionReplay { } public final class com/datadog/android/sessionreplay/SessionReplayConfiguration { - public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; - public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -47,6 +47,7 @@ public final class com/datadog/android/sessionreplay/SessionReplayConfiguration$ public final fun build ()Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; public final fun setImagePrivacy (Lcom/datadog/android/sessionreplay/ImagePrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun setPrivacy (Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; + public final fun setTextAndInputPrivacy (Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun setTouchPrivacy (Lcom/datadog/android/sessionreplay/TouchPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun startRecordingImmediately (Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun useCustomEndpoint (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; @@ -60,6 +61,14 @@ public final class com/datadog/android/sessionreplay/SessionReplayPrivacy : java public static fun values ()[Lcom/datadog/android/sessionreplay/SessionReplayPrivacy; } +public final class com/datadog/android/sessionreplay/TextAndInputPrivacy : java/lang/Enum { + public static final field MASK_ALL Lcom/datadog/android/sessionreplay/TextAndInputPrivacy; + public static final field MASK_ALL_INPUTS Lcom/datadog/android/sessionreplay/TextAndInputPrivacy; + public static final field MASK_SENSITIVE_INPUTS Lcom/datadog/android/sessionreplay/TextAndInputPrivacy; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/sessionreplay/TextAndInputPrivacy; + public static fun values ()[Lcom/datadog/android/sessionreplay/TextAndInputPrivacy; +} + public final class com/datadog/android/sessionreplay/TouchPrivacy : java/lang/Enum { public static final field HIDE Lcom/datadog/android/sessionreplay/TouchPrivacy; public static final field SHOW Lcom/datadog/android/sessionreplay/TouchPrivacy; @@ -1350,21 +1359,21 @@ public final class com/datadog/android/sessionreplay/model/ResourceMetadata$Comp } public final class com/datadog/android/sessionreplay/recorder/MappingContext { - public fun (Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;Z)V - public synthetic fun (Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;Z)V + public synthetic fun (Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lcom/datadog/android/sessionreplay/recorder/SystemInformation; public final fun component2 ()Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper; - public final fun component3 ()Lcom/datadog/android/sessionreplay/SessionReplayPrivacy; + public final fun component3 ()Lcom/datadog/android/sessionreplay/TextAndInputPrivacy; public final fun component4 ()Lcom/datadog/android/sessionreplay/ImagePrivacy; public final fun component5 ()Z - public final fun copy (Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;Z)Lcom/datadog/android/sessionreplay/recorder/MappingContext; - public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/recorder/MappingContext;Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;ZILjava/lang/Object;)Lcom/datadog/android/sessionreplay/recorder/MappingContext; + public final fun copy (Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;Z)Lcom/datadog/android/sessionreplay/recorder/MappingContext; + public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/recorder/MappingContext;Lcom/datadog/android/sessionreplay/recorder/SystemInformation;Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;Lcom/datadog/android/sessionreplay/ImagePrivacy;ZILjava/lang/Object;)Lcom/datadog/android/sessionreplay/recorder/MappingContext; public fun equals (Ljava/lang/Object;)Z public final fun getHasOptionSelectorParent ()Z public final fun getImagePrivacy ()Lcom/datadog/android/sessionreplay/ImagePrivacy; public final fun getImageWireframeHelper ()Lcom/datadog/android/sessionreplay/utils/ImageWireframeHelper; - public final fun getPrivacy ()Lcom/datadog/android/sessionreplay/SessionReplayPrivacy; public final fun getSystemInformation ()Lcom/datadog/android/sessionreplay/recorder/SystemInformation; + public final fun getTextAndInputPrivacy ()Lcom/datadog/android/sessionreplay/TextAndInputPrivacy; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -1416,7 +1425,7 @@ public abstract class com/datadog/android/sessionreplay/recorder/mapper/BaseWire public final class com/datadog/android/sessionreplay/recorder/mapper/EditTextMapper : com/datadog/android/sessionreplay/recorder/mapper/TextViewMapper { public static final field Companion Lcom/datadog/android/sessionreplay/recorder/mapper/EditTextMapper$Companion; public fun (Lcom/datadog/android/sessionreplay/utils/ViewIdentifierResolver;Lcom/datadog/android/sessionreplay/utils/ColorStringFormatter;Lcom/datadog/android/sessionreplay/utils/ViewBoundsResolver;Lcom/datadog/android/sessionreplay/utils/DrawableToColorMapper;)V - public synthetic fun resolveCapturedText (Landroid/widget/TextView;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Z)Ljava/lang/String; + public synthetic fun resolveCapturedText (Landroid/widget/TextView;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;Z)Ljava/lang/String; } public final class com/datadog/android/sessionreplay/recorder/mapper/EditTextMapper$Companion { @@ -1426,7 +1435,7 @@ public class com/datadog/android/sessionreplay/recorder/mapper/TextViewMapper : public fun (Lcom/datadog/android/sessionreplay/utils/ViewIdentifierResolver;Lcom/datadog/android/sessionreplay/utils/ColorStringFormatter;Lcom/datadog/android/sessionreplay/utils/ViewBoundsResolver;Lcom/datadog/android/sessionreplay/utils/DrawableToColorMapper;)V public synthetic fun map (Landroid/view/View;Lcom/datadog/android/sessionreplay/recorder/MappingContext;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/api/InternalLogger;)Ljava/util/List; public fun map (Landroid/widget/TextView;Lcom/datadog/android/sessionreplay/recorder/MappingContext;Lcom/datadog/android/sessionreplay/utils/AsyncJobStatusCallback;Lcom/datadog/android/api/InternalLogger;)Ljava/util/List; - protected fun resolveCapturedText (Landroid/widget/TextView;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Z)Ljava/lang/String; + protected fun resolveCapturedText (Landroid/widget/TextView;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;Z)Ljava/lang/String; } public abstract interface class com/datadog/android/sessionreplay/recorder/mapper/TraverseAllChildrenMapper : com/datadog/android/sessionreplay/recorder/mapper/WireframeMapper { diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt index e1cbea858c..058bf647ee 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt @@ -36,6 +36,7 @@ object SessionReplay { privacy = sessionReplayConfiguration.privacy, imagePrivacy = sessionReplayConfiguration.imagePrivacy, touchPrivacy = sessionReplayConfiguration.touchPrivacy, + textAndInputPrivacy = sessionReplayConfiguration.textAndInputPrivacy, customMappers = sessionReplayConfiguration.customMappers, customOptionSelectorDetectors = sessionReplayConfiguration.customOptionSelectorDetectors, sampleRate = sessionReplayConfiguration.sampleRate, diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt index 82774d1a4c..ad790cde2b 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt @@ -21,7 +21,8 @@ data class SessionReplayConfiguration internal constructor( internal val sampleRate: Float, internal val imagePrivacy: ImagePrivacy, internal val startRecordingImmediately: Boolean, - internal val touchPrivacy: TouchPrivacy + internal val touchPrivacy: TouchPrivacy, + internal val textAndInputPrivacy: TextAndInputPrivacy ) { /** @@ -32,9 +33,14 @@ data class SessionReplayConfiguration internal constructor( class Builder(@FloatRange(from = 0.0, to = 100.0) private val sampleRate: Float) { private var customEndpointUrl: String? = null private var privacy = SessionReplayPrivacy.MASK + + // indicates whether fine grained masking levels have been explicitly set + private var fineGrainedMaskingSet = false + private var imagePrivacy = ImagePrivacy.MASK_ALL private var startRecordingImmediately = true private var touchPrivacy = TouchPrivacy.HIDE + private var textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL private var extensionSupport: ExtensionSupport = NoOpExtensionSupport() /** @@ -63,8 +69,35 @@ data class SessionReplayConfiguration internal constructor( * @see SessionReplayPrivacy.MASK * @see SessionReplayPrivacy.MASK_USER_INPUT */ + @Deprecated( + message = "This method is deprecated and will be removed in future versions. " + + "Use the new fine grained masking apis instead: " + + "[setImagePrivacy], [setTouchPrivacy], [setTextAndInputPrivacy]." + ) fun setPrivacy(privacy: SessionReplayPrivacy): Builder { - this.privacy = privacy + // if fgm levels have already been explicitly set then ignore legacy privacy. + if (fineGrainedMaskingSet) return this + + when (privacy) { + SessionReplayPrivacy.ALLOW -> { + this.touchPrivacy = TouchPrivacy.SHOW + this.imagePrivacy = ImagePrivacy.MASK_NONE + this.textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS + } + + SessionReplayPrivacy.MASK_USER_INPUT -> { + this.touchPrivacy = TouchPrivacy.HIDE + this.imagePrivacy = ImagePrivacy.MASK_LARGE_ONLY + this.textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS + } + + SessionReplayPrivacy.MASK -> { + this.touchPrivacy = TouchPrivacy.HIDE + this.imagePrivacy = ImagePrivacy.MASK_ALL + this.textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL + } + } + return this } @@ -76,6 +109,7 @@ data class SessionReplayConfiguration internal constructor( * @see ImagePrivacy.MASK_ALL */ fun setImagePrivacy(level: ImagePrivacy): Builder { + fineGrainedMaskingSet = true this.imagePrivacy = level return this } @@ -87,6 +121,7 @@ data class SessionReplayConfiguration internal constructor( * @see TouchPrivacy.SHOW */ fun setTouchPrivacy(level: TouchPrivacy): Builder { + fineGrainedMaskingSet = true this.touchPrivacy = level return this } @@ -101,6 +136,19 @@ data class SessionReplayConfiguration internal constructor( return this } + /** + * Sets the text and input recording level for the Session Replay feature. + * If not specified then sensitive text will be masked by default. + * @see TextAndInputPrivacy.MASK_SENSITIVE_INPUTS + * @see TextAndInputPrivacy.MASK_ALL_INPUTS + * @see TextAndInputPrivacy.MASK_ALL + */ + fun setTextAndInputPrivacy(level: TextAndInputPrivacy): Builder { + fineGrainedMaskingSet = true + this.textAndInputPrivacy = level + return this + } + /** * Builds a [SessionReplayConfiguration] based on the current state of this Builder. */ @@ -110,6 +158,7 @@ data class SessionReplayConfiguration internal constructor( privacy = privacy, imagePrivacy = imagePrivacy, touchPrivacy = touchPrivacy, + textAndInputPrivacy = textAndInputPrivacy, customMappers = customMappers(), customOptionSelectorDetectors = extensionSupport.getOptionSelectorDetectors(), sampleRate = sampleRate, diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TextAndInputPrivacy.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TextAndInputPrivacy.kt new file mode 100644 index 0000000000..6825c465b6 --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/TextAndInputPrivacy.kt @@ -0,0 +1,32 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay + +/** + * Defines the Session Replay privacy policy when recording text and inputs. + * @see TextAndInputPrivacy.MASK_SENSITIVE_INPUTS + * @see TextAndInputPrivacy.MASK_ALL_INPUTS + * @see TextAndInputPrivacy.MASK_ALL + */ +enum class TextAndInputPrivacy { + + /** + * All text and inputs considered sensitive will be masked. + * Sensitive text includes passwords, emails and phone numbers. + */ + MASK_SENSITIVE_INPUTS, + + /** + * All inputs will be masked. + */ + MASK_ALL_INPUTS, + + /** + * All text and inputs will be masked. + */ + MASK_ALL +} diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt index c8a9f66011..d4384856ac 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt @@ -24,7 +24,7 @@ import androidx.appcompat.widget.SwitchCompat import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.MapperTypeWrapper -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.recorder.Recorder import com.datadog.android.sessionreplay.internal.recorder.SessionReplayRecorder @@ -58,7 +58,7 @@ import com.datadog.android.sessionreplay.utils.ViewIdentifierResolver internal class DefaultRecorderProvider( private val sdkCore: FeatureSdkCore, - private val privacy: SessionReplayPrivacy, + private val textAndInputPrivacy: TextAndInputPrivacy, private val imagePrivacy: ImagePrivacy, private val touchPrivacy: TouchPrivacy, private val customMappers: List>, @@ -76,9 +76,9 @@ internal class DefaultRecorderProvider( resourceDataStoreManager = resourceDataStoreManager, resourcesWriter = resourceWriter, rumContextProvider = SessionReplayRumContextProvider(sdkCore), - privacy = privacy, imagePrivacy = imagePrivacy, touchPrivacy = touchPrivacy, + textAndInputPrivacy = textAndInputPrivacy, recordWriter = recordWriter, timeProvider = SessionReplayTimeProvider(sdkCore), mappers = customMappers + builtInMappers(), diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index 295b4aeadf..9db45756b2 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -21,6 +21,7 @@ import com.datadog.android.core.sampling.Sampler import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.MapperTypeWrapper import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.net.BatchesToSegmentsMapper import com.datadog.android.sessionreplay.internal.net.SegmentRequestFactory @@ -45,8 +46,9 @@ internal class SessionReplayFeature( private val sdkCore: FeatureSdkCore, private val customEndpointUrl: String?, internal val privacy: SessionReplayPrivacy, - internal val imagePrivacy: ImagePrivacy, + internal val textAndInputPrivacy: TextAndInputPrivacy, internal val touchPrivacy: TouchPrivacy, + internal val imagePrivacy: ImagePrivacy, private val rateBasedSampler: Sampler, private val startRecordingImmediately: Boolean, private val recorderProvider: RecorderProvider @@ -58,8 +60,9 @@ internal class SessionReplayFeature( sdkCore: FeatureSdkCore, customEndpointUrl: String?, privacy: SessionReplayPrivacy, - imagePrivacy: ImagePrivacy, + textAndInputPrivacy: TextAndInputPrivacy, touchPrivacy: TouchPrivacy, + imagePrivacy: ImagePrivacy, customMappers: List>, customOptionSelectorDetectors: List, sampleRate: Float, @@ -68,13 +71,14 @@ internal class SessionReplayFeature( sdkCore, customEndpointUrl, privacy, - imagePrivacy, + textAndInputPrivacy, touchPrivacy, + imagePrivacy, RateBasedSampler(sampleRate), startRecordingImmediately, DefaultRecorderProvider( sdkCore, - privacy, + textAndInputPrivacy, imagePrivacy, touchPrivacy, customMappers, @@ -136,6 +140,9 @@ internal class SessionReplayFeature( it[SESSION_REPLAY_SAMPLE_RATE_KEY] = rateBasedSampler.getSampleRate()?.toLong() it[SESSION_REPLAY_PRIVACY_KEY] = privacy.toString().lowercase(Locale.US) it[SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY] = startRecordingImmediately + it[SESSION_REPLAY_TOUCH_PRIVACY_KEY] = touchPrivacy.toString().lowercase(Locale.US) + it[SESSION_REPLAY_IMAGE_PRIVACY_KEY] = imagePrivacy.toString().lowercase(Locale.US) + it[SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY] = textAndInputPrivacy.toString().lowercase(Locale.US) } } @@ -393,6 +400,9 @@ internal class SessionReplayFeature( const val RUM_SESSION_ID_BUS_MESSAGE_KEY = "sessionId" internal const val SESSION_REPLAY_SAMPLE_RATE_KEY = "session_replay_sample_rate" internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" + internal const val SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY = "session_replay_text_and_input_privacy" + internal const val SESSION_REPLAY_IMAGE_PRIVACY_KEY = "session_replay_image_privacy" + internal const val SESSION_REPLAY_TOUCH_PRIVACY_KEY = "session_replay_touch_privacy" internal const val SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY = "session_replay_start_immediate_recording" internal const val SESSION_REPLAY_ENABLED_KEY = diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt index 0338fa8df7..acad87f13e 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt @@ -11,7 +11,7 @@ import android.view.ViewTreeObserver import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.metrics.MethodCallSamplingRate import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.recorder.listener.WindowsOnDrawListener @@ -23,14 +23,14 @@ internal class DefaultOnDrawListenerProducer( override fun create( decorViews: List, - privacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, imagePrivacy: ImagePrivacy ): ViewTreeObserver.OnDrawListener { return WindowsOnDrawListener( zOrderedDecorViews = decorViews, recordedDataQueueHandler = recordedDataQueueHandler, snapshotProducer = snapshotProducer, - privacy = privacy, + textAndInputPrivacy = textAndInputPrivacy, imagePrivacy = imagePrivacy, internalLogger = sdkCore.internalLogger, methodCallSamplingRate = MethodCallSamplingRate.LOW.rate diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/OnDrawListenerProducer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/OnDrawListenerProducer.kt index 5aad16b0bb..69c0905e99 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/OnDrawListenerProducer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/OnDrawListenerProducer.kt @@ -9,12 +9,12 @@ package com.datadog.android.sessionreplay.internal.recorder import android.view.View import android.view.ViewTreeObserver import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy internal fun interface OnDrawListenerProducer { fun create( decorViews: List, - privacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, imagePrivacy: ImagePrivacy ): ViewTreeObserver.OnDrawListener } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt index 8008f32f25..b3dd38acd9 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt @@ -16,7 +16,7 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.MapperTypeWrapper -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.LifecycleCallback import com.datadog.android.sessionreplay.internal.SessionReplayLifecycleCallback @@ -55,7 +55,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { private val appContext: Application private val rumContextProvider: RumContextProvider - private val privacy: SessionReplayPrivacy + private val textAndInputPrivacy: TextAndInputPrivacy private val imagePrivacy: ImagePrivacy private val touchPrivacy: TouchPrivacy private val recordWriter: RecordWriter @@ -77,7 +77,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { appContext: Application, resourcesWriter: ResourcesWriter, rumContextProvider: RumContextProvider, - privacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, imagePrivacy: ImagePrivacy, touchPrivacy: TouchPrivacy, recordWriter: RecordWriter, @@ -106,7 +106,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { this.appContext = appContext this.rumContextProvider = rumContextProvider - this.privacy = privacy + this.textAndInputPrivacy = textAndInputPrivacy this.imagePrivacy = imagePrivacy this.touchPrivacy = touchPrivacy this.recordWriter = recordWriter @@ -188,9 +188,9 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { viewOnDrawInterceptor, timeProvider, internalLogger, - privacy, imagePrivacy, - touchPrivacy + touchPrivacy, + textAndInputPrivacy ) this.sessionReplayLifecycleCallback = SessionReplayLifecycleCallback(this) this.uiHandler = Handler(Looper.getMainLooper()) @@ -202,7 +202,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { constructor( appContext: Application, rumContextProvider: RumContextProvider, - privacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, imagePrivacy: ImagePrivacy, touchPrivacy: TouchPrivacy, recordWriter: RecordWriter, @@ -220,7 +220,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { ) { this.appContext = appContext this.rumContextProvider = rumContextProvider - this.privacy = privacy + this.textAndInputPrivacy = textAndInputPrivacy this.imagePrivacy = imagePrivacy this.touchPrivacy = touchPrivacy this.recordWriter = recordWriter @@ -255,7 +255,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { val windows = sessionReplayLifecycleCallback.getCurrentWindows() val decorViews = windowInspector.getGlobalWindowViews(internalLogger) windowCallbackInterceptor.intercept(windows, appContext) - viewOnDrawInterceptor.intercept(decorViews, privacy, imagePrivacy) + viewOnDrawInterceptor.intercept(decorViews, textAndInputPrivacy, imagePrivacy) } } @@ -272,7 +272,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { if (shouldRecord) { val decorViews = windowInspector.getGlobalWindowViews(internalLogger) windowCallbackInterceptor.intercept(windows, appContext) - viewOnDrawInterceptor.intercept(decorViews, privacy, imagePrivacy) + viewOnDrawInterceptor.intercept(decorViews, textAndInputPrivacy, imagePrivacy) } } @@ -281,7 +281,7 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { if (shouldRecord) { val decorViews = windowInspector.getGlobalWindowViews(internalLogger) windowCallbackInterceptor.stopIntercepting(windows) - viewOnDrawInterceptor.intercept(decorViews, privacy, imagePrivacy) + viewOnDrawInterceptor.intercept(decorViews, textAndInputPrivacy, imagePrivacy) } } } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt index ef9f64ff82..1d6ab631b3 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt @@ -11,7 +11,7 @@ import android.view.ViewGroup import androidx.annotation.UiThread import com.datadog.android.internal.profiler.withinBenchmarkSpan import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueRefs import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.recorder.MappingContext @@ -30,7 +30,7 @@ internal class SnapshotProducer( fun produce( rootView: View, systemInformation: SystemInformation, - privacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, imagePrivacy: ImagePrivacy, recordedDataQueueRefs: RecordedDataQueueRefs ): Node? { @@ -39,7 +39,7 @@ internal class SnapshotProducer( MappingContext( systemInformation = systemInformation, imageWireframeHelper = imageWireframeHelper, - privacy = privacy, + textAndInputPrivacy = textAndInputPrivacy, imagePrivacy = imagePrivacy ), LinkedList(), diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptor.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptor.kt index 99d7bc11ce..b1ee4b2bb9 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptor.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptor.kt @@ -10,7 +10,7 @@ import android.view.View import android.view.ViewTreeObserver.OnDrawListener import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import java.util.WeakHashMap internal class ViewOnDrawInterceptor( @@ -22,11 +22,12 @@ internal class ViewOnDrawInterceptor( fun intercept( decorViews: List, - sessionReplayPrivacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, imagePrivacy: ImagePrivacy ) { stopInterceptingAndRemove(decorViews) - val onDrawListener = onDrawListenerProducer.create(decorViews, sessionReplayPrivacy, imagePrivacy) + val onDrawListener = + onDrawListenerProducer.create(decorViews, textAndInputPrivacy, imagePrivacy) decorViews.forEach { decorView -> val viewTreeObserver = decorView.viewTreeObserver if (viewTreeObserver != null && viewTreeObserver.isAlive) { diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt index 4f8465371a..bf465a9845 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptor.kt @@ -10,7 +10,7 @@ import android.content.Context import android.view.Window import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.recorder.callback.NoOpWindowCallback @@ -23,9 +23,9 @@ internal class WindowCallbackInterceptor( private val viewOnDrawInterceptor: ViewOnDrawInterceptor, private val timeProvider: TimeProvider, private val internalLogger: InternalLogger, - private val privacy: SessionReplayPrivacy, private val imagePrivacy: ImagePrivacy, - private val touchPrivacy: TouchPrivacy + private val touchPrivacy: TouchPrivacy, + private val textAndInputPrivacy: TextAndInputPrivacy ) { private val wrappedWindows: WeakHashMap = WeakHashMap() @@ -59,7 +59,7 @@ internal class WindowCallbackInterceptor( timeProvider, viewOnDrawInterceptor, internalLogger, - privacy, + textAndInputPrivacy, imagePrivacy, touchPrivacy ) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt index 441c0b0b4d..010c54b87f 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallback.kt @@ -12,7 +12,7 @@ import android.view.Window import androidx.annotation.MainThread import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.recorder.ViewOnDrawInterceptor @@ -31,7 +31,7 @@ internal class RecorderWindowCallback( private val timeProvider: TimeProvider, private val viewOnDrawInterceptor: ViewOnDrawInterceptor, private val internalLogger: InternalLogger, - private val privacy: SessionReplayPrivacy, + private val privacy: TextAndInputPrivacy, private val imagePrivacy: ImagePrivacy, private val touchPrivacy: TouchPrivacy, private val copyEvent: (MotionEvent) -> MotionEvent = { @@ -182,7 +182,7 @@ internal class RecorderWindowCallback( viewOnDrawInterceptor.stopIntercepting() viewOnDrawInterceptor.intercept( decorViews = rootViews, - sessionReplayPrivacy = privacy, + textAndInputPrivacy = privacy, imagePrivacy = imagePrivacy ) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt index 58f53ac065..ae480556ae 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt @@ -14,7 +14,7 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.measureMethodCallPerf import com.datadog.android.internal.profiler.withinBenchmarkSpan import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueRefs import com.datadog.android.sessionreplay.internal.recorder.Debouncer @@ -26,7 +26,7 @@ internal class WindowsOnDrawListener( zOrderedDecorViews: List, private val recordedDataQueueHandler: RecordedDataQueueHandler, private val snapshotProducer: SnapshotProducer, - private val privacy: SessionReplayPrivacy, + private val textAndInputPrivacy: TextAndInputPrivacy, private val imagePrivacy: ImagePrivacy, private val debouncer: Debouncer = Debouncer(), private val miscUtils: MiscUtils = MiscUtils, @@ -65,7 +65,7 @@ internal class WindowsOnDrawListener( snapshotProducer.produce( rootView = it, systemInformation = systemInformation, - privacy = privacy, + textAndInputPrivacy = textAndInputPrivacy, imagePrivacy = imagePrivacy, recordedDataQueueRefs = recordedDataQueueRefs ) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/CheckableWireframeMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/CheckableWireframeMapper.kt index 7c14b9b622..b0518452a8 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/CheckableWireframeMapper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/CheckableWireframeMapper.kt @@ -10,7 +10,7 @@ import android.view.View import android.widget.Checkable import androidx.annotation.UiThread import com.datadog.android.api.InternalLogger -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.recorder.MappingContext import com.datadog.android.sessionreplay.recorder.mapper.BaseWireframeMapper @@ -40,7 +40,7 @@ internal abstract class CheckableWireframeMapper( internalLogger: InternalLogger ): List { val mainWireframes = resolveMainWireframes(view, mappingContext, asyncJobStatusCallback, internalLogger) - val checkableWireframes = if (mappingContext.privacy != SessionReplayPrivacy.ALLOW) { + val checkableWireframes = if (mappingContext.textAndInputPrivacy != TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) { resolveMaskedCheckable(view, mappingContext) } else { // Resolves checkable view regardless the state diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapper.kt index e6e8960f78..292f285a14 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapper.kt @@ -11,7 +11,7 @@ import android.widget.NumberPicker import androidx.annotation.RequiresApi import androidx.annotation.UiThread import com.datadog.android.api.InternalLogger -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.recorder.MappingContext import com.datadog.android.sessionreplay.recorder.SystemInformation @@ -73,7 +73,7 @@ internal open class NumberPickerMapper( return map( view, mappingContext.systemInformation, - mappingContext.privacy, + mappingContext.textAndInputPrivacy, prevIndexLabelId, topDividerId, selectedIndexLabelId, @@ -89,7 +89,7 @@ internal open class NumberPickerMapper( private fun map( view: NumberPicker, systemInformation: SystemInformation, - privacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, prevIndexLabelId: Long, topDividerId: Long, selectedIndexLabelId: Long, @@ -167,7 +167,7 @@ internal open class NumberPickerMapper( nextPrevLabelTextColor ) - return if (privacy == SessionReplayPrivacy.ALLOW) { + return if (textAndInputPrivacy == TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) { listOf( prevValueLabelWireframe, topDividerWireframe, diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapper.kt index 8698aa168c..621cc5e6fb 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapper.kt @@ -14,7 +14,7 @@ import android.widget.ProgressBar import androidx.annotation.RequiresApi import androidx.annotation.UiThread import com.datadog.android.api.InternalLogger -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.internal.recorder.densityNormalized import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.recorder.MappingContext @@ -69,8 +69,12 @@ internal open class ProgressBarWireframeMapper

( buildNonActiveTrackWireframe(view, trackBounds, trackColor)?.let(wireframes::add) val hasProgress = !view.isIndeterminate - val showProgress = (mappingContext.privacy == SessionReplayPrivacy.ALLOW) || - (mappingContext.privacy == SessionReplayPrivacy.MASK_USER_INPUT && showProgressWhenMaskUserInput) + val showProgress = + (mappingContext.textAndInputPrivacy == TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) || + ( + mappingContext.textAndInputPrivacy == TextAndInputPrivacy.MASK_ALL_INPUTS && + showProgressWhenMaskUserInput + ) if (hasProgress && showProgress) { val normalizedProgress = normalizedProgress(view) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt index fd4d6c21c8..c2cd516288 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapper.kt @@ -8,7 +8,7 @@ package com.datadog.android.sessionreplay.internal.recorder.mapper import android.widget.SeekBar import com.datadog.android.api.InternalLogger -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.internal.recorder.densityNormalized import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.recorder.MappingContext @@ -55,7 +55,7 @@ internal open class SeekBarWireframeMapper( normalizedProgress ) - if (mappingContext.privacy == SessionReplayPrivacy.ALLOW) { + if (mappingContext.textAndInputPrivacy == TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) { val screenDensity = mappingContext.systemInformation.screenDensity val trackHeight = ProgressBarWireframeMapper.TRACK_HEIGHT_IN_PX.densityNormalized(screenDensity) val thumbColor = getColor(view.thumbTintList, view.drawableState) ?: getDefaultColor(view) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/MappingContext.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/MappingContext.kt index cc7ef7c682..43b906b098 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/MappingContext.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/MappingContext.kt @@ -7,7 +7,7 @@ package com.datadog.android.sessionreplay.recorder import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.utils.ImageWireframeHelper /** @@ -16,7 +16,7 @@ import com.datadog.android.sessionreplay.utils.ImageWireframeHelper * expected by Datadog. * @param systemInformation as [SystemInformation] * @param imageWireframeHelper a helper tool to capture images within a View - * @param privacy the masking configuration to use when building the wireframes + * @param textAndInputPrivacy the text and input privacy level to use when building the wireframes * @param imagePrivacy the image recording configuration to use when building the wireframes * @param hasOptionSelectorParent tells if one of the parents of the current [android.view.View] * is an option selector type (e.g. time picker, date picker, drop - down list) @@ -24,7 +24,7 @@ import com.datadog.android.sessionreplay.utils.ImageWireframeHelper data class MappingContext( val systemInformation: SystemInformation, val imageWireframeHelper: ImageWireframeHelper, - val privacy: SessionReplayPrivacy, + val textAndInputPrivacy: TextAndInputPrivacy, val imagePrivacy: ImagePrivacy, val hasOptionSelectorParent: Boolean = false ) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/EditTextMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/EditTextMapper.kt index 44a0ca3a69..04c0da0a39 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/EditTextMapper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/EditTextMapper.kt @@ -9,7 +9,7 @@ package com.datadog.android.sessionreplay.recorder.mapper import android.text.InputType import android.widget.EditText import android.widget.TextView -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.internal.recorder.obfuscator.StringObfuscator import com.datadog.android.sessionreplay.utils.ColorStringFormatter import com.datadog.android.sessionreplay.utils.DrawableToColorMapper @@ -34,21 +34,25 @@ class EditTextMapper( drawableToColorMapper ) { - override fun resolveCapturedText(textView: EditText, privacy: SessionReplayPrivacy, isOption: Boolean): String { + override fun resolveCapturedText( + textView: EditText, + textAndInputPrivacy: TextAndInputPrivacy, + isOption: Boolean + ): String { val text = textView.text?.toString().orEmpty() val hint = textView.hint?.toString().orEmpty() return if (text.isNotEmpty()) { - resolveCapturedText(textView, text, privacy) + resolveCapturedText(textView, text, textAndInputPrivacy) } else { - resolveCapturedHint(hint, privacy) + resolveCapturedHint(hint, textAndInputPrivacy) } } private fun resolveCapturedText( textView: TextView, text: String, - privacy: SessionReplayPrivacy + textAndInputPrivacy: TextAndInputPrivacy ): String { val inputTypeVariation = textView.inputType and InputType.TYPE_MASK_VARIATION val inputTypeClass = textView.inputType and InputType.TYPE_MASK_CLASS @@ -61,16 +65,16 @@ class EditTextMapper( val isSensitive = isSensitiveText || isSensitiveNumber || (inputTypeClass == InputType.TYPE_CLASS_PHONE) - return when (privacy) { - SessionReplayPrivacy.ALLOW -> if (isSensitive) FIXED_INPUT_MASK else text + return when (textAndInputPrivacy) { + TextAndInputPrivacy.MASK_SENSITIVE_INPUTS -> if (isSensitive) FIXED_INPUT_MASK else text - SessionReplayPrivacy.MASK, - SessionReplayPrivacy.MASK_USER_INPUT -> FIXED_INPUT_MASK + TextAndInputPrivacy.MASK_ALL, + TextAndInputPrivacy.MASK_ALL_INPUTS -> FIXED_INPUT_MASK } } - private fun resolveCapturedHint(hint: String, privacy: SessionReplayPrivacy): String { - return if (privacy == SessionReplayPrivacy.MASK) { + private fun resolveCapturedHint(hint: String, textAndInputPrivacy: TextAndInputPrivacy): String { + return if (textAndInputPrivacy == TextAndInputPrivacy.MASK_ALL) { StringObfuscator.getStringObfuscator().obfuscate(hint) } else { hint diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/TextViewMapper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/TextViewMapper.kt index 99ebb86bdf..8d2863ed01 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/TextViewMapper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/recorder/mapper/TextViewMapper.kt @@ -11,7 +11,7 @@ import android.view.Gravity import android.widget.TextView import androidx.annotation.UiThread import com.datadog.android.api.InternalLogger -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.internal.recorder.densityNormalized import com.datadog.android.sessionreplay.internal.recorder.obfuscator.StringObfuscator import com.datadog.android.sessionreplay.model.MobileSegment @@ -82,24 +82,24 @@ open class TextViewMapper( /** * Resolves the text to record for this TextView. * @param textView the textView being mapped - * @param privacy the current privacy setting + * @param textAndInputPrivacy the current text and input privacy setting * @param isOption whether the textview is part of an option menu */ protected open fun resolveCapturedText( textView: T, - privacy: SessionReplayPrivacy, + textAndInputPrivacy: TextAndInputPrivacy, isOption: Boolean ): String { val originalText = textView.text?.toString().orEmpty() - return when (privacy) { - SessionReplayPrivacy.ALLOW -> originalText - SessionReplayPrivacy.MASK -> if (isOption) { + return when (textAndInputPrivacy) { + TextAndInputPrivacy.MASK_SENSITIVE_INPUTS -> originalText + TextAndInputPrivacy.MASK_ALL -> if (isOption) { FIXED_INPUT_MASK } else { StringObfuscator.getStringObfuscator().obfuscate(originalText) } - SessionReplayPrivacy.MASK_USER_INPUT -> if (isOption) FIXED_INPUT_MASK else originalText + TextAndInputPrivacy.MASK_ALL_INPUTS -> if (isOption) FIXED_INPUT_MASK else originalText } } @@ -112,7 +112,11 @@ open class TextViewMapper( mappingContext: MappingContext, viewGlobalBounds: GlobalBounds ): MobileSegment.Wireframe.TextWireframe { - val capturedText = resolveCapturedText(textView, mappingContext.privacy, mappingContext.hasOptionSelectorParent) + val capturedText = resolveCapturedText( + textView, + mappingContext.textAndInputPrivacy, + mappingContext.hasOptionSelectorParent + ) return MobileSegment.Wireframe.TextWireframe( id = resolveViewId(textView), x = viewGlobalBounds.x, diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt index 004c5d8cf3..36ec5e0deb 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt @@ -85,48 +85,42 @@ internal class SessionReplayConfigurationBuilderTest { } @Test - fun `M use the given privacy rule W setSessionReplayPrivacy`( - @Forgery fakePrivacy: SessionReplayPrivacy + fun `M use the given image privacy rule W setImagePrivacy`( + @Forgery fakeImagePrivacy: ImagePrivacy ) { // When val sessionReplayConfiguration = testedBuilder - .setPrivacy(fakePrivacy) + .setImagePrivacy(fakeImagePrivacy) .build() // Then - assertThat(sessionReplayConfiguration.customEndpointUrl).isNull() - assertThat(sessionReplayConfiguration.privacy).isEqualTo(fakePrivacy) - assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) - assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(TouchPrivacy.HIDE) - assertThat(sessionReplayConfiguration.customMappers).isEmpty() - assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() - assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(fakeImagePrivacy) } @Test - fun `M use the given image privacy rule W setImagePrivacy`( - @Forgery fakeImagePrivacy: ImagePrivacy + fun `M use the given touch privacy rule W setTouchPrivacy`( + @Forgery fakeTouchPrivacy: TouchPrivacy ) { // When val sessionReplayConfiguration = testedBuilder - .setImagePrivacy(fakeImagePrivacy) + .setTouchPrivacy(fakeTouchPrivacy) .build() // Then - assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(fakeImagePrivacy) + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(fakeTouchPrivacy) } @Test - fun `M use the given touch privacy rule W setTouchPrivacy`( - @Forgery fakeTouchPrivacy: TouchPrivacy + fun `M use the given text and input privacy rule W setTextAndInputPrivacy`( + @Forgery fakeTextAndInputPrivacy: TextAndInputPrivacy ) { // When val sessionReplayConfiguration = testedBuilder - .setTouchPrivacy(fakeTouchPrivacy) + .setTextAndInputPrivacy(fakeTextAndInputPrivacy) .build() // Then - assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(fakeTouchPrivacy) + assertThat(sessionReplayConfiguration.textAndInputPrivacy).isEqualTo(fakeTextAndInputPrivacy) } @Test @@ -163,4 +157,59 @@ internal class SessionReplayConfigurationBuilderTest { // Then assertThat(sessionReplayConfiguration.customMappers).isEmpty() } + + @Suppress("DEPRECATION") + @Test + fun `M not overwrite fgm W setPrivacy { fgm already set }`() { + // When + val sessionReplayConfiguration = testedBuilder + .setImagePrivacy(ImagePrivacy.MASK_ALL) + .setPrivacy(SessionReplayPrivacy.ALLOW) + .build() + + // Then + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) + } + + @Suppress("DEPRECATION") + @Test + fun `M set appropriate fgm privacy W setPrivacy { allow }`() { + // When + val sessionReplayConfiguration = testedBuilder + .setPrivacy(SessionReplayPrivacy.ALLOW) + .build() + + // Then + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_NONE) + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(TouchPrivacy.SHOW) + assertThat(sessionReplayConfiguration.textAndInputPrivacy).isEqualTo(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) + } + + @Suppress("DEPRECATION") + @Test + fun `M set appropriate fgm privacy W setPrivacy { mask_user_input }`() { + // When + val sessionReplayConfiguration = testedBuilder + .setPrivacy(SessionReplayPrivacy.MASK_USER_INPUT) + .build() + + // Then + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_LARGE_ONLY) + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(TouchPrivacy.HIDE) + assertThat(sessionReplayConfiguration.textAndInputPrivacy).isEqualTo(TextAndInputPrivacy.MASK_ALL_INPUTS) + } + + @Suppress("DEPRECATION") + @Test + fun `M set appropriate fgm privacy W setPrivacy { mask }`() { + // When + val sessionReplayConfiguration = testedBuilder + .setPrivacy(SessionReplayPrivacy.MASK) + .build() + + // Then + assertThat(sessionReplayConfiguration.imagePrivacy).isEqualTo(ImagePrivacy.MASK_ALL) + assertThat(sessionReplayConfiguration.touchPrivacy).isEqualTo(TouchPrivacy.HIDE) + assertThat(sessionReplayConfiguration.textAndInputPrivacy).isEqualTo(TextAndInputPrivacy.MASK_ALL) + } } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt index 7bcb479ba4..b22f526ec9 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayRecorderTest.kt @@ -60,7 +60,7 @@ internal class SessionReplayRecorderTest { private lateinit var mockRecordWriter: RecordWriter @Forgery - private lateinit var fakePrivacy: SessionReplayPrivacy + private lateinit var fakeTextAndInputPrivacy: TextAndInputPrivacy @Forgery private lateinit var fakeImagePrivacy: ImagePrivacy @@ -110,7 +110,7 @@ internal class SessionReplayRecorderTest { testedSessionReplayRecorder = SessionReplayRecorder( appContext = appContext.mockInstance, rumContextProvider = mockRumContextProvider, - privacy = fakePrivacy, + textAndInputPrivacy = fakeTextAndInputPrivacy, imagePrivacy = fakeImagePrivacy, touchPrivacy = fakeTouchPrivacy, recordWriter = mockRecordWriter, @@ -155,7 +155,7 @@ internal class SessionReplayRecorderTest { verify(mockWindowCallbackInterceptor).intercept(fakeActiveWindows, appContext.mockInstance) verify(mockViewOnDrawInterceptor).intercept( decorViews = fakeActiveWindowsDecorViews, - sessionReplayPrivacy = fakePrivacy, + textAndInputPrivacy = fakeTextAndInputPrivacy, imagePrivacy = fakeImagePrivacy ) } @@ -186,7 +186,7 @@ internal class SessionReplayRecorderTest { // Then verify(mockWindowCallbackInterceptor).intercept(fakeAddedWindows, appContext.mockInstance) - verify(mockViewOnDrawInterceptor).intercept(fakeNewDecorViews, fakePrivacy, fakeImagePrivacy) + verify(mockViewOnDrawInterceptor).intercept(fakeNewDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) } @Test @@ -208,7 +208,7 @@ internal class SessionReplayRecorderTest { verify(mockWindowCallbackInterceptor, never()) .intercept(fakeAddedWindows, appContext.mockInstance) verify(mockViewOnDrawInterceptor, never()) - .intercept(fakeNewDecorViews, fakePrivacy, fakeImagePrivacy) + .intercept(fakeNewDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) } @Test @@ -230,7 +230,7 @@ internal class SessionReplayRecorderTest { verify(mockWindowCallbackInterceptor, never()) .intercept(fakeAddedWindows, appContext.mockInstance) verify(mockViewOnDrawInterceptor, never()) - .intercept(fakeNewDecorViews, fakePrivacy, fakeImagePrivacy) + .intercept(fakeNewDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) } @Test @@ -249,7 +249,7 @@ internal class SessionReplayRecorderTest { // Then verify(mockWindowCallbackInterceptor).stopIntercepting(fakeAddedWindows) - verify(mockViewOnDrawInterceptor).intercept(fakeNewDecorViews, fakePrivacy, fakeImagePrivacy) + verify(mockViewOnDrawInterceptor).intercept(fakeNewDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) } @Test @@ -270,7 +270,7 @@ internal class SessionReplayRecorderTest { // Then verify(mockWindowCallbackInterceptor, never()).stopIntercepting(fakeAddedWindows) verify(mockViewOnDrawInterceptor, never()) - .intercept(fakeNewDecorViews, fakePrivacy, fakeImagePrivacy) + .intercept(fakeNewDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) } @Test @@ -289,7 +289,7 @@ internal class SessionReplayRecorderTest { // Then verify(mockWindowCallbackInterceptor, never()).stopIntercepting(fakeAddedWindows) verify(mockViewOnDrawInterceptor, never()) - .intercept(fakeNewDecorViews, fakePrivacy, fakeImagePrivacy) + .intercept(fakeNewDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) } @Test diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/MappingContextForgeryFactory.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/MappingContextForgeryFactory.kt index 50954b7e4b..77c940fdc3 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/MappingContextForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/MappingContextForgeryFactory.kt @@ -17,7 +17,7 @@ internal class MappingContextForgeryFactory : ForgeryFactory { systemInformation = forge.getForgery(), imageWireframeHelper = mock(), hasOptionSelectorParent = forge.aBool(), - privacy = forge.getForgery(), + textAndInputPrivacy = forge.getForgery(), imagePrivacy = forge.getForgery() ) } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt index dcc02db347..4e889fbba4 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt @@ -9,6 +9,7 @@ package com.datadog.android.sessionreplay.forge import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayConfiguration import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory @@ -19,6 +20,7 @@ class SessionReplayConfigurationForgeryFactory : ForgeryFactory @Forgery - lateinit var fakePrivacy: SessionReplayPrivacy + lateinit var fakeTextAndInputPrivacy: TextAndInputPrivacy @Forgery lateinit var fakeImagePrivacy: ImagePrivacy @@ -96,7 +96,7 @@ internal class SnapshotProducerTest { val snapshot = testedSnapshotProducer.produce( mockRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -123,7 +123,7 @@ internal class SnapshotProducerTest { val snapshot = testedSnapshotProducer.produce( fakeRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -150,7 +150,7 @@ internal class SnapshotProducerTest { val snapshot = testedSnapshotProducer.produce( fakeRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -177,7 +177,7 @@ internal class SnapshotProducerTest { val snapshot = testedSnapshotProducer.produce( fakeRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -208,7 +208,7 @@ internal class SnapshotProducerTest { testedSnapshotProducer.produce( mockRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -220,7 +220,7 @@ internal class SnapshotProducerTest { argumentCaptor.allValues.forEach { assertThat(it.systemInformation).isEqualTo(fakeSystemInformation) assertThat(it.imageWireframeHelper).isEqualTo(mockImageWireframeHelper) - assertThat(it.privacy).isEqualTo(fakePrivacy) + assertThat(it.textAndInputPrivacy).isEqualTo(fakeTextAndInputPrivacy) } } @@ -245,7 +245,7 @@ internal class SnapshotProducerTest { testedSnapshotProducer.produce( mockRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -280,7 +280,7 @@ internal class SnapshotProducerTest { testedSnapshotProducer.produce( mockRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -324,7 +324,7 @@ internal class SnapshotProducerTest { val snapshot = testedSnapshotProducer.produce( fakeRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) @@ -357,7 +357,7 @@ internal class SnapshotProducerTest { val snapshot = testedSnapshotProducer.produce( fakeRoot, fakeSystemInformation, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy, mockRecordedDataQueueRefs ) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptorTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptorTest.kt index 863a2ee994..2503081ba3 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptorTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/ViewOnDrawInterceptorTest.kt @@ -10,7 +10,7 @@ import android.view.View import android.view.ViewTreeObserver import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery @@ -54,7 +54,7 @@ internal class ViewOnDrawInterceptorTest { lateinit var mockOnDrawListener: ViewTreeObserver.OnDrawListener @Forgery - lateinit var fakePrivacy: SessionReplayPrivacy + lateinit var fakeTextAndInputPrivacy: TextAndInputPrivacy @Forgery lateinit var fakeImagePrivacy: ImagePrivacy @@ -68,7 +68,7 @@ internal class ViewOnDrawInterceptorTest { whenever( mockOnDrawListenerProducer.create( fakeDecorViews, - fakePrivacy, + fakeTextAndInputPrivacy, fakeImagePrivacy ) ) doReturn mockOnDrawListener @@ -82,7 +82,7 @@ internal class ViewOnDrawInterceptorTest { @Test fun `M register the OnDrawListener W intercept()`() { // When - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // Then fakeDecorViews.forEach { @@ -99,15 +99,15 @@ internal class ViewOnDrawInterceptorTest { testedInterceptor = ViewOnDrawInterceptor( internalLogger = mockInternalLogger, onDrawListenerProducer = { _, privacy, _ -> - check(privacy == fakePrivacy) { - "Expected to create an OnDrawListener with privacy $fakePrivacy but was $privacy" + check(privacy == fakeTextAndInputPrivacy) { + "Expected to create an OnDrawListener with privacy $fakeTextAndInputPrivacy but was $privacy" } mock() } ) // When - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // Then fakeDecorViews.forEach { @@ -124,7 +124,7 @@ internal class ViewOnDrawInterceptorTest { ) { _, _, _ -> mockOnDrawListener } // When - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // Then fakeDecorViews.forEach { @@ -136,7 +136,7 @@ internal class ViewOnDrawInterceptorTest { @Test fun `M register one single listener instance W intercept()`() { // When - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // Then val captor = argumentCaptor() @@ -158,7 +158,7 @@ internal class ViewOnDrawInterceptorTest { } // When - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // Then assertThat(testedInterceptor.decorOnDrawListeners).isEmpty() @@ -172,7 +172,7 @@ internal class ViewOnDrawInterceptorTest { } // When - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // Then assertThat(testedInterceptor.decorOnDrawListeners).isEmpty() @@ -181,7 +181,7 @@ internal class ViewOnDrawInterceptorTest { @Test fun `M unregister and clean the listeners W stopIntercepting(decorViews)`() { // Given - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // When testedInterceptor.stopIntercepting(fakeDecorViews) @@ -198,7 +198,7 @@ internal class ViewOnDrawInterceptorTest { @Test fun `M unregister the listeners safely W stopIntercepting(decorViews)`() { // Given - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) fakeDecorViews.forEach { whenever(it.viewTreeObserver.removeOnDrawListener(any())) doThrow IllegalStateException() } @@ -218,7 +218,7 @@ internal class ViewOnDrawInterceptorTest { @Test fun `M unregister and clean the listeners W stopIntercepting()`() { // Given - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // When testedInterceptor.stopIntercepting() @@ -236,10 +236,10 @@ internal class ViewOnDrawInterceptorTest { @Test fun `M unregister first and clean the listeners W intercepting()`() { // Given - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // When - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) // Then fakeDecorViews.forEach { @@ -255,7 +255,7 @@ internal class ViewOnDrawInterceptorTest { @Test fun `M unregister the listeners safely W stopIntercepting()`() { // Given - testedInterceptor.intercept(fakeDecorViews, fakePrivacy, fakeImagePrivacy) + testedInterceptor.intercept(fakeDecorViews, fakeTextAndInputPrivacy, fakeImagePrivacy) fakeDecorViews.forEach { whenever(it.viewTreeObserver.removeOnDrawListener(any())) doThrow IllegalStateException() } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt index 632427d2d3..68d6853d19 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/WindowCallbackInterceptorTest.kt @@ -13,7 +13,7 @@ import android.view.View import android.view.Window import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler @@ -65,7 +65,7 @@ internal class WindowCallbackInterceptorTest { lateinit var mockInternalLogger: InternalLogger @Forgery - lateinit var fakePrivacy: SessionReplayPrivacy + lateinit var fakeTextAndInputPrivacy: TextAndInputPrivacy @Forgery lateinit var fakeImagePrivacy: ImagePrivacy @@ -82,13 +82,13 @@ internal class WindowCallbackInterceptorTest { mockActivity = forge.aMockedActivity() fakeWindowsList = forge.aMockedWindowsList() testedInterceptor = WindowCallbackInterceptor( - mockRecordedDataQueueHandler, - mockViewOnDrawInterceptor, - mockTimeProvider, - mockInternalLogger, - fakePrivacy, - fakeImagePrivacy, - fakeTouchPrivacy + recordedDataQueueHandler = mockRecordedDataQueueHandler, + viewOnDrawInterceptor = mockViewOnDrawInterceptor, + timeProvider = mockTimeProvider, + internalLogger = mockInternalLogger, + imagePrivacy = fakeImagePrivacy, + touchPrivacy = fakeTouchPrivacy, + textAndInputPrivacy = fakeTextAndInputPrivacy ) } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt index cefe115b9e..05afb67ddf 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/callback/RecorderWindowCallbackTest.kt @@ -14,7 +14,7 @@ import android.view.View import android.view.Window import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler @@ -102,7 +102,7 @@ internal class RecorderWindowCallbackTest { lateinit var mockEventUtils: MotionEventUtils @Forgery - lateinit var fakePrivacy: SessionReplayPrivacy + lateinit var fakeTextAndInputPrivacy: TextAndInputPrivacy @BeforeEach fun `set up`() { @@ -121,9 +121,9 @@ internal class RecorderWindowCallbackTest { timeProvider = mockTimeProvider, viewOnDrawInterceptor = mockViewOnDrawInterceptor, internalLogger = mockInternalLogger, - privacy = fakePrivacy, imagePrivacy = ImagePrivacy.MASK_NONE, touchPrivacy = TouchPrivacy.SHOW, + privacy = fakeTextAndInputPrivacy, copyEvent = { it }, motionEventUtils = mockEventUtils, motionUpdateThresholdInNs = TEST_MOTION_UPDATE_DELAY_THRESHOLD_NS, @@ -426,7 +426,7 @@ internal class RecorderWindowCallbackTest { // Then inOrder(mockViewOnDrawInterceptor) { verify(mockViewOnDrawInterceptor).stopIntercepting() - verify(mockViewOnDrawInterceptor).intercept(fakeDecorViews, fakePrivacy, ImagePrivacy.MASK_NONE) + verify(mockViewOnDrawInterceptor).intercept(fakeDecorViews, fakeTextAndInputPrivacy, ImagePrivacy.MASK_NONE) } } @@ -466,7 +466,7 @@ internal class RecorderWindowCallbackTest { timeProvider = mockTimeProvider, viewOnDrawInterceptor = mockViewOnDrawInterceptor, internalLogger = mockInternalLogger, - privacy = fakePrivacy, + privacy = fakeTextAndInputPrivacy, imagePrivacy = ImagePrivacy.MASK_NONE, touchPrivacy = TouchPrivacy.SHOW, copyEvent = { it }, @@ -507,7 +507,7 @@ internal class RecorderWindowCallbackTest { timeProvider = mockTimeProvider, viewOnDrawInterceptor = mockViewOnDrawInterceptor, internalLogger = mockInternalLogger, - privacy = fakePrivacy, + privacy = fakeTextAndInputPrivacy, imagePrivacy = ImagePrivacy.MASK_NONE, touchPrivacy = TouchPrivacy.HIDE, copyEvent = { it }, diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt index f26d01242a..97cd137251 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt @@ -15,7 +15,7 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueRefs @@ -108,7 +108,7 @@ internal class WindowsOnDrawListenerTest { lateinit var mockContext: Context @Forgery - lateinit var fakePrivacy: SessionReplayPrivacy + lateinit var fakeTextAndInputPrivacy: TextAndInputPrivacy @Forgery lateinit var fakeImagePrivacy: ImagePrivacy @@ -130,7 +130,7 @@ internal class WindowsOnDrawListenerTest { mockSnapshotProducer.produce( eq(decorView), eq(fakeSystemInformation), - eq(fakePrivacy), + eq(fakeTextAndInputPrivacy), eq(fakeImagePrivacy), any() ) @@ -157,7 +157,7 @@ internal class WindowsOnDrawListenerTest { zOrderedDecorViews = fakeMockedDecorViews, recordedDataQueueHandler = mockRecordedDataQueueHandler, snapshotProducer = mockSnapshotProducer, - privacy = fakePrivacy, + textAndInputPrivacy = fakeTextAndInputPrivacy, imagePrivacy = fakeImagePrivacy, debouncer = mockDebouncer, miscUtils = mockMiscUtils, @@ -194,7 +194,7 @@ internal class WindowsOnDrawListenerTest { verify(mockSnapshotProducer, times(fakeWindowsSnapshots.size)).produce( rootView = any(), systemInformation = any(), - privacy = eq(fakePrivacy), + textAndInputPrivacy = eq(fakeTextAndInputPrivacy), imagePrivacy = eq(fakeImagePrivacy), recordedDataQueueRefs = argCaptor.capture() ) @@ -209,7 +209,7 @@ internal class WindowsOnDrawListenerTest { zOrderedDecorViews = emptyList(), recordedDataQueueHandler = mockRecordedDataQueueHandler, snapshotProducer = mockSnapshotProducer, - privacy = fakePrivacy, + textAndInputPrivacy = fakeTextAndInputPrivacy, imagePrivacy = fakeImagePrivacy, debouncer = mockDebouncer, miscUtils = mockMiscUtils, diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseCheckableTextViewMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseCheckableTextViewMapperTest.kt index 345c5f0501..1772af9427 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseCheckableTextViewMapperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseCheckableTextViewMapperTest.kt @@ -13,7 +13,7 @@ import android.os.Build import android.widget.Checkable import android.widget.TextView import com.datadog.android.sessionreplay.ImagePrivacy -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckableTextViewMapper.Companion.CHECK_BOX_CHECKED_DRAWABLE_INDEX import com.datadog.android.sessionreplay.internal.recorder.mapper.CheckableTextViewMapper.Companion.CHECK_BOX_NOT_CHECKED_DRAWABLE_INDEX @@ -179,7 +179,7 @@ internal abstract class BaseCheckableTextViewMapperTest : internal abstract fun mockCheckableTextView(): T internal open fun expectedCheckedShapeStyle(checkBoxColor: String): MobileSegment.ShapeStyle? { - return if (fakeMappingContext.privacy == SessionReplayPrivacy.ALLOW) { + return if (fakeMappingContext.textAndInputPrivacy == TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) { MobileSegment.ShapeStyle( backgroundColor = checkBoxColor, opacity = mockCheckableTextView.alpha @@ -196,7 +196,10 @@ internal abstract class BaseCheckableTextViewMapperTest : fun `M create ImageWireFrame W map() { checked, above M }`() { // Given val allowedMappingContext = - fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW, imagePrivacy = ImagePrivacy.MASK_LARGE_ONLY) + fakeMappingContext.copy( + textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS, + imagePrivacy = ImagePrivacy.MASK_LARGE_ONLY + ) whenever(mockButtonDrawable.intrinsicHeight).thenReturn(fakeIntrinsicDrawableHeight) whenever(mockCheckableTextView.isChecked).thenReturn(true) @@ -236,7 +239,7 @@ internal abstract class BaseCheckableTextViewMapperTest : fun `M create ImageWireFrame W map() { not checked, above M }`() { // Given val allowedMappingContext = fakeMappingContext.copy( - privacy = SessionReplayPrivacy.ALLOW, + textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS, imagePrivacy = ImagePrivacy.MASK_LARGE_ONLY ) whenever(mockButtonDrawable.intrinsicHeight).thenReturn(fakeIntrinsicDrawableHeight) diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseSwitchCompatMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseSwitchCompatMapperTest.kt index d1ceca3e1f..c48948df15 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseSwitchCompatMapperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/BaseSwitchCompatMapperTest.kt @@ -11,7 +11,7 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable.ConstantState import androidx.appcompat.widget.SwitchCompat -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.recorder.densityNormalized import com.datadog.android.sessionreplay.model.MobileSegment @@ -197,7 +197,8 @@ internal abstract class BaseSwitchCompatMapperTest : LegacyBaseWireframeMapperTe // Given whenever(mockSwitch.thumbDrawable).thenReturn(null) whenever(mockSwitch.isChecked).thenReturn(forge.aBool()) - val allowMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + val allowMappingContext = + fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) // When val resolvedWireframes = testedSwitchCompatMapper.map( @@ -216,7 +217,8 @@ internal abstract class BaseSwitchCompatMapperTest : LegacyBaseWireframeMapperTe // Given whenever(mockSwitch.trackDrawable).thenReturn(null) whenever(mockSwitch.isChecked).thenReturn(forge.aBool()) - val allowMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + val allowMappingContext = + fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) // When val resolvedWireframes = testedSwitchCompatMapper.map( diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ButtonMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ButtonMapperTest.kt index 31c7e0c8be..29f6be7934 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ButtonMapperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ButtonMapperTest.kt @@ -11,7 +11,7 @@ import android.text.InputType import android.view.Gravity import android.widget.Button import android.widget.TextView -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.recorder.mapper.BaseAsyncBackgroundWireframeMapperTest @@ -68,7 +68,7 @@ internal abstract class ButtonMapperTest : BaseAsyncBackgroundWireframeMapperTes ) ) doReturn fakeTextColorHexString - withPrivacy(privacyOption()) + withTextAndInputPrivacy(privacyOption()) testedWireframeMapper = ButtonMapper( mockViewIdentifierResolver, @@ -80,7 +80,7 @@ internal abstract class ButtonMapperTest : BaseAsyncBackgroundWireframeMapperTes abstract fun expectedPrivacyCompliantText(text: String): String - abstract fun privacyOption(): SessionReplayPrivacy + abstract fun privacyOption(): TextAndInputPrivacy @ParameterizedTest(name = "{index} (typeface: {0}, align:{2}, gravity:{3})") @MethodSource("basicParametersMatrix") diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapperTest.kt index e107a6a2aa..312bc9336d 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/NumberPickerMapperTest.kt @@ -6,7 +6,7 @@ package com.datadog.android.sessionreplay.internal.recorder.mapper -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -42,7 +42,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=ALLOW}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) val expectedSelectedLabelValue = fakeValue.toString() val expectedPrevLabelValue = (fakeValue - 1).toString() val expectedNextLabelValue = (fakeValue + 1).toString() @@ -78,7 +78,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=ALLOW, value=max }`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) fakeValue = fakeMaxValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val expectedSelectedLabelValue = fakeValue.toString() @@ -116,7 +116,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=ALLOW, value=min }`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) fakeValue = fakeMinValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val expectedSelectedLabelValue = fakeValue.toString() @@ -156,7 +156,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } .toTypedArray() whenever(mockNumberPicker.displayedValues).thenReturn(fakeDisplayedValues) @@ -198,7 +198,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) fakeValue = fakeMaxValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } @@ -241,7 +241,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) fakeValue = fakeMinValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } @@ -283,7 +283,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return empty list W map {privacy=ALLOW, prevLabelId=null}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) whenever( mockViewIdentifierResolver .resolveChildUniqueIdentifier( @@ -308,7 +308,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return empty list W map {privacy=ALLOW, nextLabelId=null}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.ALLOW) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) whenever( mockViewIdentifierResolver .resolveChildUniqueIdentifier( @@ -337,7 +337,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=MASK}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL) val expectedSelectedLabelValue = "xxx" val expectedTopDividerWireframe = fakeTopDividerWireframe() val expectedSelectedLabelWireframe = fakeSelectedLabelWireframe() @@ -364,7 +364,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=MASK, value=max}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL) fakeValue = fakeMaxValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val expectedSelectedLabelValue = "xxx" @@ -393,7 +393,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=MASK, value=min }`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL) fakeValue = fakeMinValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val expectedSelectedLabelValue = "xxx" @@ -425,7 +425,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } .toTypedArray() whenever(mockNumberPicker.displayedValues).thenReturn(fakeDisplayedValues) @@ -457,7 +457,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL) fakeValue = fakeMaxValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } @@ -491,7 +491,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL) fakeValue = fakeMinValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } @@ -528,7 +528,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=MASK_USER_INPUT}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK_USER_INPUT) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS) val expectedSelectedLabelValue = "xxx" val expectedTopDividerWireframe = fakeTopDividerWireframe() val expectedSelectedLabelWireframe = fakeSelectedLabelWireframe() @@ -555,7 +555,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=MASK_USER_INPUT, value=max}`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK_USER_INPUT) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS) fakeValue = fakeMaxValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val expectedSelectedLabelValue = "xxx" @@ -584,7 +584,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { @Test fun `M return a list of wireframes W map() {privacy=MASK_USER_INPUT, value=min }`() { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK_USER_INPUT) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS) fakeValue = fakeMinValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val expectedSelectedLabelValue = "xxx" @@ -616,7 +616,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK_USER_INPUT) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } .toTypedArray() whenever(mockNumberPicker.displayedValues).thenReturn(fakeDisplayedValues) @@ -648,7 +648,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK_USER_INPUT) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS) fakeValue = fakeMaxValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } @@ -682,7 +682,7 @@ internal class NumberPickerMapperTest : BaseNumberPickerMapperTest() { forge: Forge ) { // Given - fakeMappingContext = fakeMappingContext.copy(privacy = SessionReplayPrivacy.MASK_USER_INPUT) + fakeMappingContext = fakeMappingContext.copy(textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL_INPUTS) fakeValue = fakeMinValue whenever(mockNumberPicker.value).thenReturn(fakeValue) val fakeDisplayedValues = forge.aList(size = (fakeMaxValue - fakeMinValue + 1)) { aString() } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapperTest.kt index 362cf543bd..ed4e4dfcb4 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/ProgressBarWireframeMapperTest.kt @@ -4,7 +4,7 @@ import android.content.res.ColorStateList import android.graphics.Rect import android.os.Build import android.widget.ProgressBar -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.recorder.densityNormalized import com.datadog.android.sessionreplay.internal.recorder.mapper.SeekBarWireframeMapper.Companion.TRACK_HEIGHT_IN_PX @@ -96,7 +96,7 @@ internal class ProgressBarWireframeMapperTest : @Test fun `M return generic wireframes W map {indeterminate}`() { // Given - withPrivacy(SessionReplayPrivacy.ALLOW) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) prepareMockProgressBar(isIndeterminate = true) // When @@ -120,7 +120,7 @@ internal class ProgressBarWireframeMapperTest : @TestTargetApi(Build.VERSION_CODES.O) fun `M return partial wireframes W map {determinate, invalid track id, Android 0+}`() { // Given - withPrivacy(SessionReplayPrivacy.ALLOW) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) prepareMockProgressBar(isIndeterminate = false) mockChildUniqueIdentifier(SeekBarWireframeMapper.ACTIVE_TRACK_KEY_NAME, null) @@ -141,7 +141,7 @@ internal class ProgressBarWireframeMapperTest : @TestTargetApi(Build.VERSION_CODES.O) fun `M return partial wireframes W map {determinate, invalid non active track id, Android 0+}`() { // Given - withPrivacy(SessionReplayPrivacy.ALLOW) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) prepareMockProgressBar(isIndeterminate = false) mockChildUniqueIdentifier(SeekBarWireframeMapper.NON_ACTIVE_TRACK_KEY_NAME, null) @@ -162,7 +162,7 @@ internal class ProgressBarWireframeMapperTest : @TestTargetApi(Build.VERSION_CODES.O) fun `M return wireframes W map {determinate, privacy=ALLOW, Android 0+}`() { // Given - withPrivacy(SessionReplayPrivacy.ALLOW) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) prepareMockProgressBar(isIndeterminate = false) // When @@ -183,7 +183,7 @@ internal class ProgressBarWireframeMapperTest : @TestTargetApi(Build.VERSION_CODES.O) fun `M return wireframes W map {determinate, privacy=MASK, Android 0+}`() { // Given - withPrivacy(SessionReplayPrivacy.MASK) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL) prepareMockProgressBar(isIndeterminate = false) // When @@ -203,7 +203,7 @@ internal class ProgressBarWireframeMapperTest : @TestTargetApi(Build.VERSION_CODES.O) fun `M return wireframes W map {determinate, privacy=MASK_USER_INPUT, Android 0+}`() { // Given - withPrivacy(SessionReplayPrivacy.MASK_USER_INPUT) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL_INPUTS) prepareMockProgressBar(isIndeterminate = false) // When @@ -228,7 +228,7 @@ internal class ProgressBarWireframeMapperTest : fun `M return partial wireframes W map {determinate, invalid track id}`() { // Given fakeMinValue = 0 - withPrivacy(SessionReplayPrivacy.ALLOW) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) prepareMockProgressBar(isIndeterminate = false) mockChildUniqueIdentifier(SeekBarWireframeMapper.ACTIVE_TRACK_KEY_NAME, null) @@ -249,7 +249,7 @@ internal class ProgressBarWireframeMapperTest : fun `M return partial wireframes W map {determinate, invalid non active track id}`() { // Given fakeMinValue = 0 - withPrivacy(SessionReplayPrivacy.ALLOW) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) prepareMockProgressBar(isIndeterminate = false) mockChildUniqueIdentifier(SeekBarWireframeMapper.NON_ACTIVE_TRACK_KEY_NAME, null) @@ -270,7 +270,7 @@ internal class ProgressBarWireframeMapperTest : fun `M return wireframes W map {determinate, privacy=ALLOW}`() { // Given fakeMinValue = 0 - withPrivacy(SessionReplayPrivacy.ALLOW) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS) prepareMockProgressBar(isIndeterminate = false) // When @@ -291,7 +291,7 @@ internal class ProgressBarWireframeMapperTest : fun `M return wireframes W map {determinate, privacy=MASK}`() { // Given fakeMinValue = 0 - withPrivacy(SessionReplayPrivacy.MASK) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL) prepareMockProgressBar(isIndeterminate = false) // When @@ -311,7 +311,7 @@ internal class ProgressBarWireframeMapperTest : fun `M return wireframes W map {determinate, privacy=MASK_USER_INPUT}`() { // Given fakeMinValue = 0 - withPrivacy(SessionReplayPrivacy.MASK_USER_INPUT) + withTextAndInputPrivacy(TextAndInputPrivacy.MASK_ALL_INPUTS) prepareMockProgressBar(isIndeterminate = false) // When diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt index 4d895857bc..62ca926276 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/mapper/SeekBarWireframeMapperTest.kt @@ -5,7 +5,7 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.os.Build import android.widget.SeekBar -import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.forge.ForgeConfigurator import com.datadog.android.sessionreplay.internal.recorder.densityNormalized import com.datadog.android.sessionreplay.internal.recorder.mapper.SeekBarWireframeMapper.Companion.TRACK_HEIGHT_IN_PX @@ -119,7 +119,7 @@ internal class SeekBarWireframeMapperTest : AbstractWireframeMapperTest): String { + val imagePrivacy = featureContext[SESSION_REPLAY_IMAGE_PRIVACY_KEY] as? String + ?: SESSION_REPLAY_MASK_ALL_IMAGE_PRIVACY + val touchPrivacy = featureContext[SESSION_REPLAY_TOUCH_PRIVACY_KEY] as? String + ?: SESSION_REPLAY_MASK_ALL_TOUCH_PRIVACY + val textAndInputPrivacy = + featureContext[SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY] as? String + ?: SESSION_REPLAY_MASK_ALL_TEXT_PRIVACY + + return if (isMaskNone(imagePrivacy, touchPrivacy, textAndInputPrivacy)) { + SESSION_REPLAY_MASK_NONE_PRIVACY + } else if (isMaskInputs(imagePrivacy, touchPrivacy, textAndInputPrivacy)) { + SESSION_REPLAY_MASK_INPUTS_PRIVACY + } else { + SESSION_REPLAY_MASK_ALL_PRIVACY + } + } + + private fun isMaskNone(imagePrivacy: String, touchPrivacy: String, textAndInputPrivacy: String): Boolean { + return touchPrivacy.lowercase(Locale.US) == SESSION_REPLAY_MASK_NONE_TOUCH_PRIVACY && + imagePrivacy.lowercase(Locale.US) == SESSION_REPLAY_MASK_NONE_IMAGE_PRIVACY && + textAndInputPrivacy.lowercase(Locale.US) == SESSION_REPLAY_MASK_NONE_TEXT_PRIVACY + } + + private fun isMaskInputs(imagePrivacy: String, touchPrivacy: String, textAndInputPrivacy: String): Boolean { + return touchPrivacy.lowercase(Locale.US) == SESSION_REPLAY_MASK_NONE_TOUCH_PRIVACY && + imagePrivacy.lowercase(Locale.US) == SESSION_REPLAY_MASK_NONE_IMAGE_PRIVACY && + textAndInputPrivacy.lowercase(Locale.US) == SESSION_REPLAY_MASK_INPUTS_TEXT_PRIVACY + } + private fun buildWebViewEventConsumer( sdkCore: FeatureSdkCore, logsSampleRate: Float, @@ -161,6 +191,7 @@ object WebViewTracking { null } } + private fun resolveReplayFeature(sdkCore: FeatureSdkCore): WebViewReplayFeature? { ( sdkCore.getFeature(WebViewReplayFeature.WEB_REPLAY_FEATURE_NAME) @@ -182,6 +213,7 @@ object WebViewTracking { null } } + private fun resolveLogsFeature(sdkCore: FeatureSdkCore): WebViewLogsFeature? { ( sdkCore.getFeature(WebViewLogsFeature.WEB_LOGS_FEATURE_NAME) @@ -224,8 +256,23 @@ object WebViewTracking { } internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" + + internal const val SESSION_REPLAY_MASK_NONE_PRIVACY = "allow" + internal const val SESSION_REPLAY_MASK_INPUTS_PRIVACY = "mask_user_input" internal const val SESSION_REPLAY_MASK_ALL_PRIVACY = "mask" + internal const val SESSION_REPLAY_MASK_NONE_TOUCH_PRIVACY = "show" + internal const val SESSION_REPLAY_MASK_ALL_TOUCH_PRIVACY = "hide" + internal const val SESSION_REPLAY_MASK_NONE_IMAGE_PRIVACY = "mask_none" + internal const val SESSION_REPLAY_MASK_ALL_IMAGE_PRIVACY = "mask_all" + internal const val SESSION_REPLAY_MASK_NONE_TEXT_PRIVACY = "mask_sensitive_inputs" + internal const val SESSION_REPLAY_MASK_INPUTS_TEXT_PRIVACY = "mask_all_inputs" + internal const val SESSION_REPLAY_MASK_ALL_TEXT_PRIVACY = "mask_all" + + internal const val SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY = "session_replay_text_and_input_privacy" + internal const val SESSION_REPLAY_IMAGE_PRIVACY_KEY = "session_replay_image_privacy" + internal const val SESSION_REPLAY_TOUCH_PRIVACY_KEY = "session_replay_touch_privacy" + internal const val JAVA_SCRIPT_NOT_ENABLED_WARNING_MESSAGE = "You are trying to enable the WebView" + "tracking but the java script capability was not enabled for the given WebView." diff --git a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt index 9a316cc132..64d2bae23c 100644 --- a/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt +++ b/features/dd-sdk-android-webview/src/test/kotlin/com/datadog/android/webview/WebViewTrackingTest.kt @@ -33,7 +33,6 @@ import com.datadog.android.webview.internal.rum.WebViewRumFeature import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery -import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -156,13 +155,91 @@ internal class WebViewTrackingTest { } @Test - fun `M extract and provide the SR privacy level W enable {privacy level provided}`( - @Forgery fakeUrls: List, - @StringForgery fakePrivacyLevel: String + fun `M convert to correct legacy privacy W enable { allow }`( + @Forgery fakeUrls: List + ) { + // Given + val mockSrFeatureContext = mapOf( + WebViewTracking.SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_NONE_TEXT_PRIVACY, + WebViewTracking.SESSION_REPLAY_TOUCH_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_NONE_TOUCH_PRIVACY, + WebViewTracking.SESSION_REPLAY_IMAGE_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_NONE_IMAGE_PRIVACY + ) + whenever(mockCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn + mockSrFeatureContext + val fakeHosts = fakeUrls.map { it.host } + val mockSettings: WebSettings = mock { + whenever(it.javaScriptEnabled).thenReturn(true) + } + val mockWebView: WebView = mock { + whenever(it.settings).thenReturn(mockSettings) + } + val argumentCaptor = argumentCaptor() + + // When + WebViewTracking.enable(mockWebView, fakeHosts, sdkCore = mockCore) + + // Then + verify(mockWebView).addJavascriptInterface( + argumentCaptor.capture(), + eq(WebViewTracking.DATADOG_EVENT_BRIDGE_NAME) + ) + assertThat( + argumentCaptor.firstValue.getPrivacyLevel() + ).isEqualTo(WebViewTracking.SESSION_REPLAY_MASK_NONE_PRIVACY) + } + + @Test + fun `M convert to correct legacy privacy W enable { mask_input }`( + @Forgery fakeUrls: List + ) { + // Given + val mockSrFeatureContext = mapOf( + WebViewTracking.SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_INPUTS_TEXT_PRIVACY, + WebViewTracking.SESSION_REPLAY_TOUCH_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_NONE_TOUCH_PRIVACY, + WebViewTracking.SESSION_REPLAY_IMAGE_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_NONE_IMAGE_PRIVACY + ) + whenever(mockCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn + mockSrFeatureContext + val fakeHosts = fakeUrls.map { it.host } + val mockSettings: WebSettings = mock { + whenever(it.javaScriptEnabled).thenReturn(true) + } + val mockWebView: WebView = mock { + whenever(it.settings).thenReturn(mockSettings) + } + val argumentCaptor = argumentCaptor() + + // When + WebViewTracking.enable(mockWebView, fakeHosts, sdkCore = mockCore) + + // Then + verify(mockWebView).addJavascriptInterface( + argumentCaptor.capture(), + eq(WebViewTracking.DATADOG_EVENT_BRIDGE_NAME) + ) + assertThat( + argumentCaptor.firstValue.getPrivacyLevel() + ).isEqualTo(WebViewTracking.SESSION_REPLAY_MASK_INPUTS_PRIVACY) + } + + @Test + fun `M convert to correct legacy privacy W enable { mask_all }`( + @Forgery fakeUrls: List ) { // Given val mockSrFeatureContext = mapOf( - WebViewTracking.SESSION_REPLAY_PRIVACY_KEY to fakePrivacyLevel + WebViewTracking.SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_ALL_TEXT_PRIVACY, + WebViewTracking.SESSION_REPLAY_TOUCH_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_NONE_TOUCH_PRIVACY, + WebViewTracking.SESSION_REPLAY_IMAGE_PRIVACY_KEY + to WebViewTracking.SESSION_REPLAY_MASK_NONE_IMAGE_PRIVACY ) whenever(mockCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn mockSrFeatureContext @@ -183,7 +260,9 @@ internal class WebViewTrackingTest { argumentCaptor.capture(), eq(WebViewTracking.DATADOG_EVENT_BRIDGE_NAME) ) - assertThat(argumentCaptor.firstValue.getPrivacyLevel()).isEqualTo(fakePrivacyLevel) + assertThat( + argumentCaptor.firstValue.getPrivacyLevel() + ).isEqualTo(WebViewTracking.SESSION_REPLAY_MASK_ALL_PRIVACY) } @Test diff --git a/instrumented/integration/src/main/kotlin/com/datadog/android/sdk/integration/sessionreplay/BaseSessionReplayActivity.kt b/instrumented/integration/src/main/kotlin/com/datadog/android/sdk/integration/sessionreplay/BaseSessionReplayActivity.kt index 08051e53ad..25f09e9b13 100644 --- a/instrumented/integration/src/main/kotlin/com/datadog/android/sdk/integration/sessionreplay/BaseSessionReplayActivity.kt +++ b/instrumented/integration/src/main/kotlin/com/datadog/android/sdk/integration/sessionreplay/BaseSessionReplayActivity.kt @@ -56,6 +56,7 @@ internal abstract class BaseSessionReplayActivity : AppCompatActivity() { .forEach { it() } } + @Suppress("DEPRECATION") open fun sessionReplayConfiguration(privacy: SessionReplayPrivacy, sampleRate: Float): SessionReplayConfiguration { return RuntimeConfig.sessionReplayConfigBuilder(sampleRate) .setPrivacy(privacy) diff --git a/sample/benchmark/src/main/java/com/datadog/benchmark/sample/benchmark/DatadogBenchmark.kt b/sample/benchmark/src/main/java/com/datadog/benchmark/sample/benchmark/DatadogBenchmark.kt index ecf23a8e0b..c09f60ea5e 100644 --- a/sample/benchmark/src/main/java/com/datadog/benchmark/sample/benchmark/DatadogBenchmark.kt +++ b/sample/benchmark/src/main/java/com/datadog/benchmark/sample/benchmark/DatadogBenchmark.kt @@ -50,6 +50,7 @@ internal class DatadogBenchmark(config: Config) { meter.stopGauges() } + @Suppress("DEPRECATION") private fun enableSessionReplay() { val sessionReplayConfig = SessionReplayConfiguration .Builder(SAMPLE_IN_ALL_SESSIONS) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt index be038f1bfc..23415ed7a7 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt @@ -157,6 +157,7 @@ class SampleApplication : Application() { val rumConfig = createRumConfiguration() Rum.enable(rumConfig) + @Suppress("DEPRECATION") val sessionReplayConfig = SessionReplayConfiguration.Builder(SAMPLE_IN_ALL_SESSIONS) .apply { if (BuildConfig.DD_OVERRIDE_SESSION_REPLAY_URL.isNotBlank()) { From 73d8245bdcd393aabbc20539e5be869e7e184fec Mon Sep 17 00:00:00 2001 From: luyi Date: Thu, 5 Sep 2024 17:09:30 +0200 Subject: [PATCH 010/111] RUM-5985: Add isContainer attribute to session replay span --- dd-sdk-android-internal/api/apiSurface | 4 +-- .../api/dd-sdk-android-internal.api | 9 +++++-- .../internal/profiler/BenchmarkSpanExt.kt | 7 ++++- .../internal/profiler/BenchmarkTracer.kt | 6 ++++- .../internal/recorder/BenchmarkExt.kt | 27 +++++++++++++++++++ .../internal/recorder/SnapshotProducer.kt | 3 +-- .../listener/WindowsOnDrawListener.kt | 4 +-- .../BenchmarkSpanToSpanEventMapper.kt | 12 ++++++--- .../benchmark/profiler/DDBenchmarkTracer.kt | 10 ++++++- 9 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/BenchmarkExt.kt diff --git a/dd-sdk-android-internal/api/apiSurface b/dd-sdk-android-internal/api/apiSurface index 6fb18f4f0d..dac3f3d126 100644 --- a/dd-sdk-android-internal/api/apiSurface +++ b/dd-sdk-android-internal/api/apiSurface @@ -4,9 +4,9 @@ interface com.datadog.android.internal.profiler.BenchmarkSpan fun stop() interface com.datadog.android.internal.profiler.BenchmarkSpanBuilder fun startSpan(): BenchmarkSpan -fun withinBenchmarkSpan(String, BenchmarkSpan.() -> T): T +fun withinBenchmarkSpan(String, Map = emptyMap(), BenchmarkSpan.() -> T): T interface com.datadog.android.internal.profiler.BenchmarkTracer - fun spanBuilder(String): BenchmarkSpanBuilder + fun spanBuilder(String, Map = emptyMap()): BenchmarkSpanBuilder object com.datadog.android.internal.profiler.GlobalBenchmark fun register(BenchmarkProfiler) fun get(): BenchmarkProfiler diff --git a/dd-sdk-android-internal/api/dd-sdk-android-internal.api b/dd-sdk-android-internal/api/dd-sdk-android-internal.api index 4e08d9b272..0d686f1758 100644 --- a/dd-sdk-android-internal/api/dd-sdk-android-internal.api +++ b/dd-sdk-android-internal/api/dd-sdk-android-internal.api @@ -11,11 +11,16 @@ public abstract interface class com/datadog/android/internal/profiler/BenchmarkS } public final class com/datadog/android/internal/profiler/BenchmarkSpanExtKt { - public static final fun withinBenchmarkSpan (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun withinBenchmarkSpan (Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static synthetic fun withinBenchmarkSpan$default (Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; } public abstract interface class com/datadog/android/internal/profiler/BenchmarkTracer { - public abstract fun spanBuilder (Ljava/lang/String;)Lcom/datadog/android/internal/profiler/BenchmarkSpanBuilder; + public abstract fun spanBuilder (Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/internal/profiler/BenchmarkSpanBuilder; +} + +public final class com/datadog/android/internal/profiler/BenchmarkTracer$DefaultImpls { + public static synthetic fun spanBuilder$default (Lcom/datadog/android/internal/profiler/BenchmarkTracer;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/internal/profiler/BenchmarkSpanBuilder; } public final class com/datadog/android/internal/profiler/GlobalBenchmark { diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkSpanExt.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkSpanExt.kt index 6ce10b67b1..dd831d10c9 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkSpanExt.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkSpanExt.kt @@ -11,16 +11,21 @@ package com.datadog.android.internal.profiler * @param T the type returned by the lambda * @param operationName the name of the [BenchmarkSpan] created around the lambda * (default is `true`) + * @param additionalProperties Additional properties for this span. * @param block the lambda function traced by this newly created [BenchmarkSpan] * */ inline fun withinBenchmarkSpan( operationName: String, + additionalProperties: Map = emptyMap(), block: BenchmarkSpan.() -> T ): T { val tracer = GlobalBenchmark.get().getTracer("dd-sdk-android") - val spanBuilder = tracer.spanBuilder(operationName) + val spanBuilder = tracer.spanBuilder( + operationName, + additionalProperties + ) val span = spanBuilder.startSpan() diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkTracer.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkTracer.kt index 07ad124579..b940819d62 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkTracer.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/profiler/BenchmarkTracer.kt @@ -19,7 +19,11 @@ interface BenchmarkTracer { * Returns a new [BenchmarkSpanBuilder]. * * @param spanName The name of the returned span. + * @param additionalProperties Additional properties for this span. * @return a new [BenchmarkSpanBuilder]. */ - fun spanBuilder(spanName: String): BenchmarkSpanBuilder + fun spanBuilder( + spanName: String, + additionalProperties: Map = emptyMap() + ): BenchmarkSpanBuilder } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/BenchmarkExt.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/BenchmarkExt.kt new file mode 100644 index 0000000000..de8297b53c --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/BenchmarkExt.kt @@ -0,0 +1,27 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal.recorder + +import com.datadog.android.internal.profiler.BenchmarkSpan +import com.datadog.android.internal.profiler.withinBenchmarkSpan + +private const val ATTRIBUTE_CONTAINER = "attribute.container" + +/** + * A wrap function of [withinSRBenchmarkSpan] dedicated to session replay span recording. + */ +internal inline fun withinSRBenchmarkSpan( + spanName: String, + isContainer: Boolean = false, + block: BenchmarkSpan.() -> T +): T { + return withinBenchmarkSpan( + spanName, + mapOf(ATTRIBUTE_CONTAINER to isContainer.toString()), + block + ) +} diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt index ef9f64ff82..2f4fdec3e0 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SnapshotProducer.kt @@ -9,7 +9,6 @@ package com.datadog.android.sessionreplay.internal.recorder import android.view.View import android.view.ViewGroup import androidx.annotation.UiThread -import com.datadog.android.internal.profiler.withinBenchmarkSpan import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueRefs @@ -55,7 +54,7 @@ internal class SnapshotProducer( parents: LinkedList, recordedDataQueueRefs: RecordedDataQueueRefs ): Node? { - return withinBenchmarkSpan(view::class.java.simpleName) { + return withinSRBenchmarkSpan(view::class.java.simpleName, view is ViewGroup) { val traversedTreeView = treeViewTraversal.traverse(view, mappingContext, recordedDataQueueRefs) val nextTraversalStrategy = traversedTreeView.nextActionStrategy val resolvedWireframes = traversedTreeView.mappedWireframes diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt index 58f53ac065..00eb4665fa 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt @@ -12,13 +12,13 @@ import androidx.annotation.MainThread import androidx.annotation.UiThread import com.datadog.android.api.InternalLogger import com.datadog.android.api.feature.measureMethodCallPerf -import com.datadog.android.internal.profiler.withinBenchmarkSpan import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplayPrivacy import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueHandler import com.datadog.android.sessionreplay.internal.async.RecordedDataQueueRefs import com.datadog.android.sessionreplay.internal.recorder.Debouncer import com.datadog.android.sessionreplay.internal.recorder.SnapshotProducer +import com.datadog.android.sessionreplay.internal.recorder.withinSRBenchmarkSpan import com.datadog.android.sessionreplay.internal.utils.MiscUtils import java.lang.ref.WeakReference @@ -58,7 +58,7 @@ internal class WindowsOnDrawListener( METHOD_CALL_CAPTURE_RECORD, methodCallSamplingRate ) { - withinBenchmarkSpan(BENCHMARK_SPAN_SNAPSHOT_PRODUCER) { + withinSRBenchmarkSpan(BENCHMARK_SPAN_SNAPSHOT_PRODUCER, isContainer = true) { val recordedDataQueueRefs = RecordedDataQueueRefs(recordedDataQueueHandler) recordedDataQueueRefs.recordedDataQueueItem = item rootViews.mapNotNull { diff --git a/tools/benchmark/src/main/java/com/datadog/benchmark/internal/BenchmarkSpanToSpanEventMapper.kt b/tools/benchmark/src/main/java/com/datadog/benchmark/internal/BenchmarkSpanToSpanEventMapper.kt index 9ea628d619..8b8d1429be 100644 --- a/tools/benchmark/src/main/java/com/datadog/benchmark/internal/BenchmarkSpanToSpanEventMapper.kt +++ b/tools/benchmark/src/main/java/com/datadog/benchmark/internal/BenchmarkSpanToSpanEventMapper.kt @@ -28,19 +28,23 @@ internal class BenchmarkSpanToSpanEventMapper { duration = durationNanos, start = spanData.startEpochNanos, error = 0, // error is not needed in benchmark - meta = resolveMeta(), + meta = resolveMeta(spanData), metrics = resolveMetrics() ) } - private fun resolveMeta(): SpanEvent.Meta { - // TODO: RUM-5985 Fill SpanEvent with useful meta data + private fun resolveMeta(spanData: SpanData): SpanEvent.Meta { + val map = mutableMapOf() + spanData.attributes.forEach { attributeKey, value -> + map[attributeKey.key] = value.toString() + } return SpanEvent.Meta( version = "", dd = SpanEvent.Dd(), span = SpanEvent.Span(), tracer = SpanEvent.Tracer(version = ""), - usr = SpanEvent.Usr() + usr = SpanEvent.Usr(), + additionalProperties = map ) } diff --git a/tools/benchmark/src/main/java/com/datadog/benchmark/profiler/DDBenchmarkTracer.kt b/tools/benchmark/src/main/java/com/datadog/benchmark/profiler/DDBenchmarkTracer.kt index 5aae30c169..3fa15bf6f8 100644 --- a/tools/benchmark/src/main/java/com/datadog/benchmark/profiler/DDBenchmarkTracer.kt +++ b/tools/benchmark/src/main/java/com/datadog/benchmark/profiler/DDBenchmarkTracer.kt @@ -16,10 +16,18 @@ import io.opentelemetry.context.Context */ class DDBenchmarkTracer(private val tracer: Tracer) : BenchmarkTracer { - override fun spanBuilder(spanName: String): BenchmarkSpanBuilder { + override fun spanBuilder( + spanName: String, + additionalProperties: Map + ): BenchmarkSpanBuilder { return DDBenchmarkSpanBuilder( tracer .spanBuilder(spanName) + .apply { + additionalProperties.forEach { + this.setAttribute(it.key, it.value) + } + } .setParent(Context.current()) ) } From c123409543ed603c11ab5aa892509d07f2e8e17d Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Sun, 8 Sep 2024 13:46:18 +0300 Subject: [PATCH 011/111] RUM-6096: Fix placeholder dimensions --- .../resources/DefaultImageWireframeHelper.kt | 49 ++++++++++--------- .../DefaultImageWireframeHelperTest.kt | 11 +---- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelper.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelper.kt index 0e4caec6e5..2c0d42b3ae 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelper.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelper.kt @@ -90,13 +90,18 @@ internal class DefaultImageWireframeHelper( } val density = displayMetrics.density + val drawableWidthDp = drawableProperties.drawableWidth.densityNormalized(density).toLong() + val drawableHeightDp = drawableProperties.drawableHeight.densityNormalized(density).toLong() if (imagePrivacy == ImagePrivacy.MASK_ALL) { return createContentPlaceholderWireframe( id = id, - view = view, - density = density, - label = MASK_ALL_CONTENT_LABEL + x = x, + y = y, + width = drawableWidthDp, + height = drawableHeightDp, + label = MASK_ALL_CONTENT_LABEL, + clipping = clipping ) } @@ -104,15 +109,15 @@ internal class DefaultImageWireframeHelper( if (shouldMaskContextualImage(imagePrivacy, usePIIPlaceholder, drawable, density)) { return createContentPlaceholderWireframe( id = id, - view = view, - density = density, - label = MASK_CONTEXTUAL_CONTENT_LABEL + x = x, + y = y, + width = drawableWidthDp, + height = drawableHeightDp, + label = MASK_CONTEXTUAL_CONTENT_LABEL, + clipping = clipping ) } - val drawableWidthDp = drawableProperties.drawableWidth.densityNormalized(density).toLong() - val drawableHeightDp = drawableProperties.drawableHeight.densityNormalized(density).toLong() - val imageWireframe = MobileSegment.Wireframe.ImageWireframe( id = id, @@ -237,24 +242,22 @@ internal class DefaultImageWireframeHelper( } private fun createContentPlaceholderWireframe( - view: View, id: Long, - density: Float, - label: String + x: Long, + y: Long, + width: Long, + height: Long, + label: String, + clipping: MobileSegment.WireframeClip? ): MobileSegment.Wireframe.PlaceholderWireframe { - val coordinates = IntArray(2) - @Suppress("UnsafeThirdPartyFunctionCall") // this will always have size >= 2 - view.getLocationOnScreen(coordinates) - val viewX = coordinates[0].densityNormalized(density).toLong() - val viewY = coordinates[1].densityNormalized(density).toLong() - return MobileSegment.Wireframe.PlaceholderWireframe( id, - viewX, - viewY, - view.width.densityNormalized(density).toLong(), - view.height.densityNormalized(density).toLong(), - label = label + x, + y, + width, + height, + label = label, + clip = clipping ) } diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt index 29cca29982..e73652d4ad 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/resources/DefaultImageWireframeHelperTest.kt @@ -670,13 +670,6 @@ internal class DefaultImageWireframeHelperTest { mockDisplayMetrics.density = 1f whenever(mockContext.applicationContext).thenReturn(mockContext) val mockView: View = mock { - whenever(it.getLocationOnScreen(any())).thenAnswer { location -> - val coords = location.arguments[0] as IntArray - coords[0] = fakeGlobalX - coords[1] = fakeGlobalY - null - } - whenever(it.resources).thenReturn(mockResources) whenever(it.context).thenReturn(mockContext) } @@ -686,8 +679,8 @@ internal class DefaultImageWireframeHelperTest { view = mockView, imagePrivacy = ImagePrivacy.MASK_LARGE_ONLY, currentWireframeIndex = forge.aPositiveInt(), - x = forge.aPositiveLong(), - y = forge.aPositiveLong(), + x = fakeGlobalX.toLong(), + y = fakeGlobalY.toLong(), width = forge.aPositiveInt(), height = forge.aPositiveInt(), drawable = mockDrawable, From d260b581d669e16b627ac89a75bd306ab6c33074 Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Mon, 9 Sep 2024 08:54:08 +0200 Subject: [PATCH 012/111] Prepare hotfix 2.13.1 --- CHANGELOG.md | 4 ++++ .../main/kotlin/com/datadog/gradle/config/AndroidConfig.kt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bda28a5c88..617047dc58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.13.1 / 2024-09-09 + +* [BUGFIX] Stop upload worker on upload failure. See [#2242](https://github.com/DataDog/dd-sdk-android/pull/2242) + # 2.13.0 / 2024-09-03 * [FEATURE] Create Benchmark module to collect performance metrics. See [#2141](https://github.com/DataDog/dd-sdk-android/pull/2141) diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt index 7562b607a9..655dfe4610 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/config/AndroidConfig.kt @@ -20,7 +20,7 @@ object AndroidConfig { const val MIN_SDK_FOR_WEAR = 23 const val BUILD_TOOLS_VERSION = "34.0.0" - val VERSION = Version(2, 13, 0, Version.Type.Release) + val VERSION = Version(2, 13, 1, Version.Type.Release) } // TODO RUM-628 Switch to Java 17 bytecode From bdfa827a47fbc4fc8dfc501e640d24f7dde86bfe Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:39:07 +0300 Subject: [PATCH 013/111] RUM-5761: fgm instead of legacy privacy in config telemetry --- .../internal/TelemetryEventHandler.kt | 4 --- .../TelemetryConfigurationEventAssert.kt | 30 ++++++++++++++++--- .../internal/TelemetryEventHandlerTest.kt | 24 ++++++++++----- .../internal/SessionReplayFeature.kt | 2 -- .../internal/SessionReplayFeatureTest.kt | 4 +-- .../android/webview/WebViewTracking.kt | 2 -- 6 files changed, 44 insertions(+), 22 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 85c59a0309..cc31b50884 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -252,8 +252,6 @@ internal class TelemetryEventHandler( as? Long val startRecordingImmediately = sessionReplayFeatureContext[SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY] as? Boolean - val legacySessionReplayPrivacy = sessionReplayFeatureContext[SESSION_REPLAY_PRIVACY_KEY] - as? String val sessionReplayImagePrivacy = sessionReplayFeatureContext[SESSION_REPLAY_IMAGE_PRIVACY_KEY] as? String val sessionReplayTouchPrivacy = @@ -320,7 +318,6 @@ internal class TelemetryEventHandler( tracerApiVersion = openTelemetryApiVersion, trackNetworkRequests = trackNetworkRequests, sessionReplaySampleRate = sessionReplaySampleRate, - defaultPrivacyLevel = legacySessionReplayPrivacy, imagePrivacyLevel = sessionReplayImagePrivacy, touchPrivacyLevel = sessionReplayTouchPrivacy, textAndInputPrivacyLevel = sessionReplayTextAndInputPrivacy, @@ -402,7 +399,6 @@ internal class TelemetryEventHandler( internal const val IS_OPENTELEMETRY_ENABLED_CONTEXT_KEY = "is_opentelemetry_enabled" internal const val OPENTELEMETRY_API_VERSION_CONTEXT_KEY = "opentelemetry_api_version" internal const val SESSION_REPLAY_SAMPLE_RATE_KEY = "session_replay_sample_rate" - internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" internal const val SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY = "session_replay_text_and_input_privacy" internal const val SESSION_REPLAY_IMAGE_PRIVACY_KEY = "session_replay_image_privacy" internal const val SESSION_REPLAY_TOUCH_PRIVACY_KEY = "session_replay_touch_privacy" diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt index e952f8f987..bfd29dc99d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryConfigurationEventAssert.kt @@ -349,12 +349,34 @@ internal class TelemetryConfigurationEventAssert(actual: TelemetryConfigurationE return this } - fun hasSessionReplayPrivacy(expected: String?): TelemetryConfigurationEventAssert { - assertThat(actual.telemetry.configuration.defaultPrivacyLevel) + fun hasSessionReplayImagePrivacy(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.imagePrivacyLevel) .overridingErrorMessage( - "Expected event data to have telemetry.configuration.defaultPrivacyLevel" + + "Expected event data to have telemetry.configuration.imagePrivacyLevel" + " $expected " + - "but was ${actual.telemetry.configuration.defaultPrivacyLevel}" + "but was ${actual.telemetry.configuration.imagePrivacyLevel}" + ) + .isEqualTo(expected) + return this + } + + fun hasSessionReplayTouchPrivacy(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.touchPrivacyLevel) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.touchPrivacyLevel" + + " $expected " + + "but was ${actual.telemetry.configuration.touchPrivacyLevel}" + ) + .isEqualTo(expected) + return this + } + + fun hasSessionReplayTextAndInputPrivacy(expected: String?): TelemetryConfigurationEventAssert { + assertThat(actual.telemetry.configuration.textAndInputPrivacyLevel) + .overridingErrorMessage( + "Expected event data to have telemetry.configuration.textAndInputPrivacyLevel" + + " $expected " + + "but was ${actual.telemetry.configuration.textAndInputPrivacyLevel}" ) .isEqualTo(expected) return this diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index 907ba04c2f..f3fddae894 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -479,7 +479,9 @@ internal class TelemetryEventHandlerTest { assertConfigEventMatchesRawEvent(firstValue, configRawEvent) assertThat(firstValue).hasSessionReplaySampleRate(null) assertThat(firstValue).hasStartRecordingImmediately(null) - assertThat(firstValue).hasSessionReplayPrivacy(null) + assertThat(firstValue).hasSessionReplayImagePrivacy(null) + assertThat(firstValue).hasSessionReplayTouchPrivacy(null) + assertThat(firstValue).hasSessionReplayTextAndInputPrivacy(null) } } @@ -489,13 +491,17 @@ internal class TelemetryEventHandlerTest { ) { // Given val fakeSampleRate = forge.aPositiveLong() - val fakeSessionReplayPrivacy = forge.aString() + val fakeSessionReplayImagePrivacy = forge.aString() + val fakeSessionReplayTouchPrivacy = forge.aString() + val fakeSessionReplayTextAndInputPrivacy = forge.aString() val fakeSessionReplayIsStartImmediately = forge.aBool() val fakeSessionReplayContext = mutableMapOf( - TelemetryEventHandler.SESSION_REPLAY_PRIVACY_KEY to fakeSessionReplayPrivacy, TelemetryEventHandler.SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY to fakeSessionReplayIsStartImmediately, - TelemetryEventHandler.SESSION_REPLAY_SAMPLE_RATE_KEY to fakeSampleRate + TelemetryEventHandler.SESSION_REPLAY_SAMPLE_RATE_KEY to fakeSampleRate, + TelemetryEventHandler.SESSION_REPLAY_IMAGE_PRIVACY_KEY to fakeSessionReplayImagePrivacy, + TelemetryEventHandler.SESSION_REPLAY_TOUCH_PRIVACY_KEY to fakeSessionReplayTouchPrivacy, + TelemetryEventHandler.SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY to fakeSessionReplayTextAndInputPrivacy ) whenever(mockSdkCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn fakeSessionReplayContext @@ -510,7 +516,9 @@ internal class TelemetryEventHandlerTest { assertConfigEventMatchesRawEvent(firstValue, configRawEvent) assertThat(firstValue).hasSessionReplaySampleRate(fakeSampleRate) assertThat(firstValue).hasStartRecordingImmediately(fakeSessionReplayIsStartImmediately) - assertThat(firstValue).hasSessionReplayPrivacy(fakeSessionReplayPrivacy) + assertThat(firstValue).hasSessionReplayImagePrivacy(fakeSessionReplayImagePrivacy) + assertThat(firstValue).hasSessionReplayTouchPrivacy(fakeSessionReplayTouchPrivacy) + assertThat(firstValue).hasSessionReplayTextAndInputPrivacy(fakeSessionReplayTextAndInputPrivacy) } } @@ -520,10 +528,8 @@ internal class TelemetryEventHandlerTest { ) { // Given val fakeSampleRate = forge.aNullable { aString() } - val fakeSessionReplayPrivacy = forge.aNullable { aLong() } val fakeSessionReplayIsStartedImmediatley = forge.aNullable { aString() } val fakeSessionReplayContext = mutableMapOf( - TelemetryEventHandler.SESSION_REPLAY_PRIVACY_KEY to fakeSessionReplayPrivacy, TelemetryEventHandler.SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY to fakeSessionReplayIsStartedImmediatley, TelemetryEventHandler.SESSION_REPLAY_SAMPLE_RATE_KEY to fakeSampleRate @@ -541,7 +547,9 @@ internal class TelemetryEventHandlerTest { assertConfigEventMatchesRawEvent(firstValue, configRawEvent) assertThat(firstValue).hasSessionReplaySampleRate(null) assertThat(firstValue).hasStartRecordingImmediately(null) - assertThat(firstValue).hasSessionReplayPrivacy(null) + assertThat(firstValue).hasSessionReplayImagePrivacy(null) + assertThat(firstValue).hasSessionReplayTouchPrivacy(null) + assertThat(firstValue).hasSessionReplayTextAndInputPrivacy(null) } } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index 9db45756b2..1b28872329 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -138,7 +138,6 @@ internal class SessionReplayFeature( initialized.set(true) sdkCore.updateFeatureContext(SESSION_REPLAY_FEATURE_NAME) { it[SESSION_REPLAY_SAMPLE_RATE_KEY] = rateBasedSampler.getSampleRate()?.toLong() - it[SESSION_REPLAY_PRIVACY_KEY] = privacy.toString().lowercase(Locale.US) it[SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY] = startRecordingImmediately it[SESSION_REPLAY_TOUCH_PRIVACY_KEY] = touchPrivacy.toString().lowercase(Locale.US) it[SESSION_REPLAY_IMAGE_PRIVACY_KEY] = imagePrivacy.toString().lowercase(Locale.US) @@ -399,7 +398,6 @@ internal class SessionReplayFeature( const val RUM_KEEP_SESSION_BUS_MESSAGE_KEY = "keepSession" const val RUM_SESSION_ID_BUS_MESSAGE_KEY = "sessionId" internal const val SESSION_REPLAY_SAMPLE_RATE_KEY = "session_replay_sample_rate" - internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" internal const val SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY = "session_replay_text_and_input_privacy" internal const val SESSION_REPLAY_IMAGE_PRIVACY_KEY = "session_replay_image_privacy" internal const val SESSION_REPLAY_TOUCH_PRIVACY_KEY = "session_replay_touch_privacy" diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt index 8f0066e729..ceb9c8447b 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeatureTest.kt @@ -176,12 +176,12 @@ internal class SessionReplayFeatureTest { firstValue.invoke(updatedContext) assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_SAMPLE_RATE_KEY]) .isEqualTo(fakeConfiguration.sampleRate.toLong()) - assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_PRIVACY_KEY]) - .isEqualTo(fakeConfiguration.privacy.toString().lowercase(Locale.US)) assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_START_IMMEDIATE_RECORDING_KEY]) .isEqualTo(true) assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_IMAGE_PRIVACY_KEY]) .isEqualTo(fakeConfiguration.imagePrivacy.toString().lowercase(Locale.US)) + assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_TOUCH_PRIVACY_KEY]) + .isEqualTo(fakeConfiguration.touchPrivacy.toString().lowercase(Locale.US)) assertThat(updatedContext[SessionReplayFeature.SESSION_REPLAY_TEXT_AND_INPUT_PRIVACY_KEY]) .isEqualTo(fakeConfiguration.textAndInputPrivacy.toString().lowercase(Locale.US)) } diff --git a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/WebViewTracking.kt b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/WebViewTracking.kt index de0d05d566..7fdd026d68 100644 --- a/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/WebViewTracking.kt +++ b/features/dd-sdk-android-webview/src/main/kotlin/com/datadog/android/webview/WebViewTracking.kt @@ -255,8 +255,6 @@ object WebViewTracking { } } - internal const val SESSION_REPLAY_PRIVACY_KEY = "session_replay_privacy" - internal const val SESSION_REPLAY_MASK_NONE_PRIVACY = "allow" internal const val SESSION_REPLAY_MASK_INPUTS_PRIVACY = "mask_user_input" internal const val SESSION_REPLAY_MASK_ALL_PRIVACY = "mask" From 86dac338568f5234ccb43478085a7019990d92ee Mon Sep 17 00:00:00 2001 From: luyi Date: Fri, 6 Sep 2024 18:55:52 +0200 Subject: [PATCH 014/111] RUM-6093:Add TimeBank in session replay recorder for dynamic optimisation --- .../internal/recorder/Debouncer.kt | 31 +++- .../internal/recorder/RecordingTimeBank.kt | 53 +++++++ .../internal/recorder/TimeBank.kt | 26 ++++ .../listener/WindowsOnDrawListener.kt | 2 +- .../internal/recorder/DebouncerTest.kt | 10 +- .../recorder/RecordingTimeBankTest.kt | 135 ++++++++++++++++++ 6 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/RecordingTimeBank.kt create mode 100644 features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/TimeBank.kt create mode 100644 features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/RecordingTimeBankTest.kt diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt index 749219efbd..2a13f02d82 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt @@ -8,11 +8,14 @@ package com.datadog.android.sessionreplay.internal.recorder import android.os.Handler import android.os.Looper +import com.datadog.android.api.InternalLogger import java.util.concurrent.TimeUnit internal class Debouncer( private val handler: Handler = Handler(Looper.getMainLooper()), - private val maxRecordDelayInNs: Long = MAX_DELAY_THRESHOLD_NS + private val maxRecordDelayInNs: Long = MAX_DELAY_THRESHOLD_NS, + private val timeBank: TimeBank = RecordingTimeBank(), + private val internalLogger: InternalLogger ) { private var lastTimeRecordWasPerformed = 0L @@ -37,8 +40,28 @@ internal class Debouncer( } private fun executeRunnable(runnable: Runnable) { - runnable.run() - lastTimeRecordWasPerformed = System.nanoTime() + runInTimeBalance { + runnable.run() + lastTimeRecordWasPerformed = System.nanoTime() + } + } + + private fun runInTimeBalance(block: () -> Unit) { + if (timeBank.updateAndCheck(System.nanoTime())) { + val startTimeInNano = System.nanoTime() + block() + val endTimeInNano = System.nanoTime() + timeBank.consume(endTimeInNano - startTimeInNano) + } else { + // TODO RUM-5188 report with telemetry about the frames skipped + internalLogger.log( + InternalLogger.Level.INFO, + InternalLogger.Target.MAINTAINER, + messageBuilder = { + MSG_FRAME_SKIP + } + ) + } } companion object { @@ -47,5 +70,7 @@ internal class Debouncer( // one frame time internal const val DEBOUNCE_TIME_IN_MS: Long = 64 + + private const val MSG_FRAME_SKIP = "Session Replay skipped this recording due to the time limit" } } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/RecordingTimeBank.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/RecordingTimeBank.kt new file mode 100644 index 0000000000..1d9b377896 --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/RecordingTimeBank.kt @@ -0,0 +1,53 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal.recorder + +import java.util.concurrent.TimeUnit +import kotlin.math.min + +/** + * Time Bank is a concept representing an allocated execution quota per second. For example, if the quota is set to + * 100 milliseconds per second, it means that within any given second, no more than 100 milliseconds can be used for + * executing operations. If the full quota of 100 milliseconds has already been used within a second, further execution + * is not permitted until the next second begins and the quota is recharged. Conversely, if less than 100 milliseconds + * has been used and the second has not yet elapsed, execution may continue until the quota is reached. + */ +internal class RecordingTimeBank( + private val maxTimeBalancePerSecondInMs: Long = DEFAULT_MAX_TIME_BALANCE_PER_SEC_IN_MS +) : TimeBank { + + // The normalized factor of balance increasing by time. If increasing 100ms balance in the bank takes 1000ms, + // then the factor will be 100ms/1000ms = 0.1f + private val balanceFactor = maxTimeBalancePerSecondInMs.toDouble() / TimeUnit.SECONDS.toMillis(1) + + @Volatile + private var recordingTimeBalanceInNano = TimeUnit.MILLISECONDS.toNanos(maxTimeBalancePerSecondInMs) + + @Volatile + private var lastCheckTime: Long = 0 + + override fun consume(executionTime: Long) { + recordingTimeBalanceInNano -= executionTime + } + + override fun updateAndCheck(timestamp: Long): Boolean { + increaseTimeBank(timestamp) + lastCheckTime = timestamp + return recordingTimeBalanceInNano >= 0 + } + + private fun increaseTimeBank(timestamp: Long) { + val timePassedSinceLastExecution = timestamp - lastCheckTime + recordingTimeBalanceInNano += (timePassedSinceLastExecution * balanceFactor).toLong() + recordingTimeBalanceInNano = + min(TimeUnit.MILLISECONDS.toNanos(maxTimeBalancePerSecondInMs), recordingTimeBalanceInNano) + } + + companion object { + private const val DEFAULT_MAX_TIME_BALANCE_PER_SEC_IN_MS = 100L + } +} diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/TimeBank.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/TimeBank.kt new file mode 100644 index 0000000000..bd12f3e300 --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/TimeBank.kt @@ -0,0 +1,26 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.internal.recorder + +import com.datadog.tools.annotation.NoOpImplementation + +@NoOpImplementation +internal interface TimeBank { + + /** + * Called to consume execution time from the bank. + */ + fun consume(executionTime: Long) + + /** + * Called to update time bank balance and check if the given timestamp + * is allowed according to the current time balance. + * + * @return true if the given timestamp is allowed by time bank to execute a task, false otherwise. + */ + fun updateAndCheck(timestamp: Long): Boolean +} diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt index 58f53ac065..fbc80ef27c 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt @@ -28,9 +28,9 @@ internal class WindowsOnDrawListener( private val snapshotProducer: SnapshotProducer, private val privacy: SessionReplayPrivacy, private val imagePrivacy: ImagePrivacy, - private val debouncer: Debouncer = Debouncer(), private val miscUtils: MiscUtils = MiscUtils, private val internalLogger: InternalLogger, + private val debouncer: Debouncer = Debouncer(internalLogger = internalLogger), private val methodCallSamplingRate: Float ) : ViewTreeObserver.OnDrawListener { diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt index 2db8ddbbc9..e5f629ebd1 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt @@ -7,6 +7,7 @@ package com.datadog.android.sessionreplay.internal.recorder import android.os.Handler +import com.datadog.android.api.InternalLogger import com.datadog.android.sessionreplay.forge.ForgeConfigurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -38,11 +39,18 @@ internal class DebouncerTest { @Mock lateinit var mockHandler: Handler + @Mock + lateinit var mockInternalLogger: InternalLogger + lateinit var testedDebouncer: Debouncer @BeforeEach fun `set up`() { - testedDebouncer = Debouncer(mockHandler, TEST_MAX_DELAY_THRESHOLD_IN_NS) + testedDebouncer = Debouncer( + mockHandler, + TEST_MAX_DELAY_THRESHOLD_IN_NS, + internalLogger = mockInternalLogger + ) } @Test diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/RecordingTimeBankTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/RecordingTimeBankTest.kt new file mode 100644 index 0000000000..3a821bc06d --- /dev/null +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/recorder/RecordingTimeBankTest.kt @@ -0,0 +1,135 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sessionreplay.recorder + +import com.datadog.android.sessionreplay.forge.ForgeConfigurator +import com.datadog.android.sessionreplay.internal.recorder.RecordingTimeBank +import com.datadog.android.sessionreplay.internal.recorder.TimeBank +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.quality.Strictness +import java.util.concurrent.TimeUnit + +@Extensions( + ExtendWith(MockitoExtension::class), + ExtendWith(ForgeExtension::class) +) +@MockitoSettings(strictness = Strictness.LENIENT) +@ForgeConfiguration(ForgeConfigurator::class) +class RecordingTimeBankTest { + + private lateinit var recordingTimeBank: TimeBank + + @BeforeEach + fun `set up`() { + recordingTimeBank = RecordingTimeBank(TEST_MAX_BALANCE_IN_MS) + } + + @Test + fun `M allow the first execution W check`(forge: Forge) { + // Given + val timestamp = forge.aLong(min = 0) + + // When + val actual = recordingTimeBank.updateAndCheck(timestamp) + + // Then + assertThat(actual).isTrue() + } + + @Test + fun `M skip the next execution W previous consume out the balance`(forge: Forge) { + // Given + val firstTimestamp = forge.aLong(min = 0) + val firstExecutionTime = forge.aLong( + min = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS), + max = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS) * 100 + ) + val interval = forge.aLong(min = 0, max = firstExecutionTime) + val secondTimestamp = forge.aLong( + min = firstTimestamp + firstExecutionTime, + max = firstTimestamp + firstExecutionTime + interval + ) + + // When + recordingTimeBank.updateAndCheck(firstTimestamp) + recordingTimeBank.consume(firstExecutionTime) + val actual = recordingTimeBank.updateAndCheck(secondTimestamp) + + // Then + assertThat(actual).isFalse() + } + + @Test + fun `M allow the next execution W balance is recovery`(forge: Forge) { + // Given + val firstTimestamp = forge.aLong(min = 0) + val firstExecutionTime = forge.aLong( + min = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS), + max = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS) * 100 + ) + val interval = forge.aLong(min = 0, max = firstExecutionTime) + val secondTimestamp = forge.aLong( + min = firstTimestamp + firstExecutionTime, + max = firstTimestamp + firstExecutionTime + interval + ) + + val thirdTimestamp = + forge.aLong( + min = secondTimestamp + firstExecutionTime / + ((TimeUnit.SECONDS.toMillis(1) / TEST_MAX_BALANCE_IN_MS)) + ) + + // When + recordingTimeBank.updateAndCheck(firstTimestamp) + recordingTimeBank.consume(firstExecutionTime) + recordingTimeBank.updateAndCheck(secondTimestamp) + check(!recordingTimeBank.updateAndCheck(secondTimestamp)) + val actual = recordingTimeBank.updateAndCheck(thirdTimestamp) + + // Then + assertThat(actual).isTrue() + } + + @Test + fun `M allow everything W set max balance more than 1000ms per sec`(forge: Forge) { + val maxBalancePerSecondInMs = forge.aLong(min = 1000) + recordingTimeBank = RecordingTimeBank(maxBalancePerSecondInMs) + + // Given + val firstTimestamp = forge.aLong(min = 0) + val firstExecutionTime = forge.aLong( + min = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS), + max = TimeUnit.MILLISECONDS.toNanos(TEST_MAX_BALANCE_IN_MS) * 100 + ) + val interval = forge.aLong(min = 0, max = firstExecutionTime) + val secondTimestamp = forge.aLong( + min = firstTimestamp + firstExecutionTime, + max = firstTimestamp + firstExecutionTime + interval + ) + + // When + recordingTimeBank.updateAndCheck(firstTimestamp) + recordingTimeBank.consume(firstExecutionTime) + val actual = recordingTimeBank.updateAndCheck(secondTimestamp) + + // Then + assertThat(actual).isEqualTo(true) + } + + companion object { + private const val TEST_MAX_BALANCE_IN_MS = 100L + } +} From 6a258c41a59d24ff933d73f34b2031f6e471fb1a Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Tue, 10 Sep 2024 10:44:31 +0200 Subject: [PATCH 015/111] RUM-6104 ensure UploadWorker uses the sdk instance name --- .../android/core/internal/DatadogCore.kt | 1 + .../lifecycle/ProcessLifecycleCallback.kt | 3 +- .../core/internal/utils/WorkManagerUtils.kt | 9 ++++-- .../error/internal/DatadogExceptionHandler.kt | 2 +- .../datadog/android/core/DatadogCoreTest.kt | 17 +++++++++- .../lifecycle/ProcessLifecycleCallbackTest.kt | 28 ++++++++++------ .../internal/utils/WorkManagerUtilsTest.kt | 32 +++++++++++-------- .../internal/DatadogExceptionHandlerTest.kt | 20 ++++++++---- detekt_custom.yml | 8 +++-- 9 files changed, 83 insertions(+), 37 deletions(-) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt index 4599a08245..7f2d53b592 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt @@ -421,6 +421,7 @@ internal class DatadogCore( processLifecycleMonitor = ProcessLifecycleMonitor( ProcessLifecycleCallback( appContext, + name, internalLogger ) ).apply { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt index ea35934c2b..7795cad754 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt @@ -16,6 +16,7 @@ import java.lang.ref.WeakReference internal class ProcessLifecycleCallback( appContext: Context, + internal val instanceName: String, private val internalLogger: InternalLogger ) : ProcessLifecycleMonitor.Callback { @@ -37,7 +38,7 @@ internal class ProcessLifecycleCallback( override fun onStopped() { contextWeakRef.get()?.let { if (WorkManager.isInitialized()) { - triggerUploadWorker(it, internalLogger) + triggerUploadWorker(it, instanceName, internalLogger) } } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt index 53018d96ae..aacedcf665 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt @@ -8,13 +8,13 @@ package com.datadog.android.core.internal.utils import android.content.Context import androidx.work.Constraints +import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import com.datadog.android.api.InternalLogger import com.datadog.android.core.internal.data.upload.UploadWorker -import java.lang.IllegalStateException import java.util.concurrent.TimeUnit internal const val CANCEL_ERROR_MESSAGE = "Error cancelling the UploadWorker" @@ -40,7 +40,11 @@ internal fun cancelUploadWorker(context: Context, internalLogger: InternalLogger } @Suppress("TooGenericExceptionCaught") -internal fun triggerUploadWorker(context: Context, internalLogger: InternalLogger) { +internal fun triggerUploadWorker( + context: Context, + instanceName: String, + internalLogger: InternalLogger +) { try { val workManager = WorkManager.getInstance(context) val constraints = Constraints.Builder() @@ -50,6 +54,7 @@ internal fun triggerUploadWorker(context: Context, internalLogger: InternalLogge .setConstraints(constraints) .addTag(TAG_DATADOG_UPLOAD) .setInitialDelay(DELAY_MS, TimeUnit.MILLISECONDS) + .setInputData(Data.Builder().putString(UploadWorker.DATADOG_INSTANCE_NAME, instanceName).build()) .build() workManager.enqueueUniqueWork( UPLOAD_WORKER_NAME, diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt index 7cad55a33c..643f871b22 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/error/internal/DatadogExceptionHandler.kt @@ -90,7 +90,7 @@ internal class DatadogExceptionHandler( // trigger a task to send the logs ASAP contextRef.get()?.let { if (WorkManager.isInitialized()) { - triggerUploadWorker(it, sdkCore.internalLogger) + triggerUploadWorker(it, sdkCore.name, sdkCore.internalLogger) } } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt index 83f6fda220..010a48bd58 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt @@ -19,6 +19,7 @@ import com.datadog.android.core.internal.ContextProvider import com.datadog.android.core.internal.CoreFeature import com.datadog.android.core.internal.DatadogCore import com.datadog.android.core.internal.SdkFeature +import com.datadog.android.core.internal.lifecycle.ProcessLifecycleCallback import com.datadog.android.core.internal.lifecycle.ProcessLifecycleMonitor import com.datadog.android.core.internal.net.DefaultFirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.net.info.NetworkInfoProvider @@ -803,6 +804,20 @@ internal class DatadogCoreTest { assertThat(testedCore.isDeveloperModeEnabled).isFalse() } + @Test + fun `M register process lifecycle monitor W initialize()`() { + // Then + argumentCaptor { + verify(appContext.mockInstance) + .registerActivityLifecycleCallbacks(capture()) + assertThat(lastValue).isInstanceOf(ProcessLifecycleMonitor::class.java) + val callback = (lastValue as ProcessLifecycleMonitor).callback + assertThat(callback).isInstanceOf(ProcessLifecycleCallback::class.java) + val processLifecycleCallback = callback as ProcessLifecycleCallback + assertThat(processLifecycleCallback.instanceName).isEqualTo(fakeInstanceName) + } + } + @Test fun `M unregister process lifecycle monitor W stop()`() { // Given @@ -812,7 +827,7 @@ internal class DatadogCoreTest { testedCore.stop() // Then - argumentCaptor { + argumentCaptor { verify(appContext.mockInstance, times(expectedInvocations)) .unregisterActivityLifecycleCallbacks(capture()) assertThat(lastValue).isInstanceOf(ProcessLifecycleMonitor::class.java) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt index 003b7cb47c..29b1a1353c 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt @@ -20,8 +20,10 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.setStaticValue +import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -32,7 +34,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any -import org.mockito.kotlin.argThat +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -58,9 +60,12 @@ internal class ProcessLifecycleCallbackTest { @Mock lateinit var mockInternalLogger: InternalLogger + @StringForgery + lateinit var fakeInstanceName: String + @BeforeEach fun `set up`() { - testedCallback = ProcessLifecycleCallback(appContext.mockInstance, mockInternalLogger) + testedCallback = ProcessLifecycleCallback(appContext.mockInstance, fakeInstanceName, mockInternalLogger) } @AfterEach @@ -84,14 +89,17 @@ internal class ProcessLifecycleCallbackTest { testedCallback.onStopped() // Then - verify(mockWorkManager).enqueueUniqueWork( - eq(UPLOAD_WORKER_NAME), - eq(ExistingWorkPolicy.REPLACE), - argThat { - this.workSpec.workerClassName == UploadWorker::class.java.canonicalName && - this.tags.contains(TAG_DATADOG_UPLOAD) - } - ) + argumentCaptor { + verify(mockWorkManager).enqueueUniqueWork( + eq(UPLOAD_WORKER_NAME), + eq(ExistingWorkPolicy.REPLACE), + capture() + ) + val workSpec = lastValue.workSpec + assertThat(workSpec.workerClassName).isEqualTo(UploadWorker::class.java.canonicalName) + assertThat(workSpec.input.getString(UploadWorker.DATADOG_INSTANCE_NAME)).isEqualTo(fakeInstanceName) + assertThat(lastValue.tags).contains(TAG_DATADOG_UPLOAD) + } } @Test diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt index 05c01364fd..53d53a6a0d 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt @@ -8,7 +8,6 @@ package com.datadog.android.core.internal.utils import android.app.Application import androidx.work.ExistingWorkPolicy -import androidx.work.NetworkType import androidx.work.OneTimeWorkRequest import androidx.work.impl.WorkManagerImpl import com.datadog.android.api.InternalLogger @@ -20,8 +19,10 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.setStaticValue +import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -32,7 +33,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any -import org.mockito.kotlin.argThat +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.mock @@ -56,6 +57,9 @@ internal class WorkManagerUtilsTest { @Mock lateinit var mockInternalLogger: InternalLogger + @StringForgery + lateinit var fakeInstanceName: String + @BeforeEach fun `set up`() { CoreFeature.disableKronosBackgroundSync = true @@ -102,24 +106,26 @@ internal class WorkManagerUtilsTest { WorkManagerImpl::class.java.setStaticValue("sDefaultInstance", mockWorkManager) // When - triggerUploadWorker(appContext.mockInstance, mockInternalLogger) + triggerUploadWorker(appContext.mockInstance, fakeInstanceName, mockInternalLogger) // Then - verify(mockWorkManager).enqueueUniqueWork( - eq(UPLOAD_WORKER_NAME), - eq(ExistingWorkPolicy.REPLACE), - argThat { - this.workSpec.workerClassName == UploadWorker::class.java.canonicalName && - this.tags.contains(TAG_DATADOG_UPLOAD) && - this.workSpec.constraints.requiredNetworkType == NetworkType.NOT_ROAMING - } - ) + argumentCaptor { + verify(mockWorkManager).enqueueUniqueWork( + eq(UPLOAD_WORKER_NAME), + eq(ExistingWorkPolicy.REPLACE), + capture() + ) + val workSpec = lastValue.workSpec + assertThat(workSpec.workerClassName).isEqualTo(UploadWorker::class.java.canonicalName) + assertThat(workSpec.input.getString(UploadWorker.DATADOG_INSTANCE_NAME)).isEqualTo(fakeInstanceName) + assertThat(lastValue.tags).contains(TAG_DATADOG_UPLOAD) + } } @Test fun `it will handle the trigger exception if WorkManager was not correctly instantiated`() { // When - triggerUploadWorker(appContext.mockInstance, mockInternalLogger) + triggerUploadWorker(appContext.mockInstance, fakeInstanceName, mockInternalLogger) // Then verifyNoInteractions(mockWorkManager) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt index e5fecf2830..1de4994168 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt @@ -31,6 +31,7 @@ import com.datadog.tools.unit.extensions.config.TestConfiguration import com.datadog.tools.unit.setStaticValue import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat @@ -46,7 +47,6 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any -import org.mockito.kotlin.argThat import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq @@ -96,11 +96,15 @@ internal class DatadogExceptionHandlerTest { @Forgery lateinit var fakeThrowable: Throwable + @StringForgery + lateinit var fakeInstanceName: String + @BeforeEach fun `set up`() { whenever(mockSdkCore.getFeature(Feature.LOGS_FEATURE_NAME)) doReturn mockLogsFeatureScope whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger + whenever(mockSdkCore.name) doReturn fakeInstanceName CoreFeature.disableKronosBackgroundSync = true @@ -448,15 +452,17 @@ internal class DatadogExceptionHandlerTest { testedHandler.uncaughtException(currentThread, fakeThrowable) // Then - verify(mockWorkManager) - .enqueueUniqueWork( + argumentCaptor { + verify(mockWorkManager).enqueueUniqueWork( eq(UPLOAD_WORKER_NAME), eq(ExistingWorkPolicy.REPLACE), - argThat { - this.workSpec.workerClassName == UploadWorker::class.java.canonicalName && - this.tags.contains(TAG_DATADOG_UPLOAD) - } + capture() ) + val workSpec = lastValue.workSpec + assertThat(workSpec.workerClassName).isEqualTo(UploadWorker::class.java.canonicalName) + assertThat(workSpec.input.getString(UploadWorker.DATADOG_INSTANCE_NAME)).isEqualTo(fakeInstanceName) + assertThat(lastValue.tags).contains(TAG_DATADOG_UPLOAD) + } } // region Forward to RUM diff --git a/detekt_custom.yml b/detekt_custom.yml index 83eed153d8..39354216bd 100644 --- a/detekt_custom.yml +++ b/detekt_custom.yml @@ -152,6 +152,7 @@ datadog: - "androidx.metrics.performance.JankStats.createAndTrack(android.view.Window, androidx.metrics.performance.JankStats.OnFrameListener):java.lang.IllegalStateException" - "androidx.navigation.Navigation.findNavController(android.app.Activity, kotlin.Int):java.lang.IllegalStateException" - "androidx.work.WorkManager.enqueueUniqueWork(kotlin.String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest):java.util.concurrent.RejectedExecutionException,java.lang.NullPointerException" + - "androidx.work.Data.Builder.build():java.lang.IllegalStateException" # endregion # region Java File - "java.io.ByteArrayOutputStream.write(kotlin.ByteArray, kotlin.Int, kotlin.Int):java.lang.IndexOutOfBoundsException" @@ -475,6 +476,8 @@ datadog: - "android.graphics.Rect.width()" # endregion # region Androidx APIs + - "androidx.appcompat.widget.DatadogActionBarContainerAccessor.constructor(androidx.appcompat.widget.ActionBarContainer)" + - "androidx.appcompat.widget.DatadogActionBarContainerAccessor.getBackgroundDrawable()" - "androidx.compose.foundation.interaction.DragInteraction.Start.constructor()" - "androidx.compose.runtime.DisposableEffect(kotlin.Any?, kotlin.Any?, kotlin.Function1)" - "androidx.compose.runtime.DisposableEffectScope.onDispose(kotlin.Function0)" @@ -506,6 +509,8 @@ datadog: - "androidx.work.Constraints.Builder.build()" - "androidx.work.Constraints.Builder.constructor()" - "androidx.work.Constraints.Builder.setRequiredNetworkType(androidx.work.NetworkType)" + - "androidx.work.Data.Builder.constructor()" + - "androidx.work.Data.Builder.putString(kotlin.String, kotlin.String?)" - "androidx.work.Data.getString(kotlin.String)" - "androidx.work.ListenableWorker.Result.success()" - "androidx.work.OneTimeWorkRequest.Builder(java.lang.Class)" @@ -514,11 +519,10 @@ datadog: - "androidx.work.OneTimeWorkRequest.Builder.constructor(java.lang.Class)" - "androidx.work.OneTimeWorkRequest.Builder.setConstraints(androidx.work.Constraints)" - "androidx.work.OneTimeWorkRequest.Builder.setInitialDelay(kotlin.Long, java.util.concurrent.TimeUnit)" + - "androidx.work.OneTimeWorkRequest.Builder.setInputData(androidx.work.Data)" - "androidx.work.WorkManager.cancelAllWorkByTag(kotlin.String)" - "androidx.work.WorkManager.getInstance(android.content.Context)" - "androidx.work.WorkManager.isInitialized()" - - "androidx.appcompat.widget.DatadogActionBarContainerAccessor.constructor(androidx.appcompat.widget.ActionBarContainer)" - - "androidx.appcompat.widget.DatadogActionBarContainerAccessor.getBackgroundDrawable()" # endregion # region Google Material - "com.google.android.material.tabs.TabLayout.TabView.getChildAt(kotlin.Int)" From cefc19cf30183c95701143dd841d776a85d0561c Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Wed, 11 Sep 2024 09:23:50 +0200 Subject: [PATCH 016/111] RUM-6104 when triggering the uploader for an instance, only remove workRequest for that specific instance --- .../internal/lifecycle/ProcessLifecycleCallback.kt | 2 +- .../android/core/internal/utils/WorkManagerUtils.kt | 10 +++++++--- .../internal/lifecycle/ProcessLifecycleCallbackTest.kt | 4 ++-- .../core/internal/utils/WorkManagerUtilsTest.kt | 8 ++++---- .../error/internal/DatadogExceptionHandlerTest.kt | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt index 7795cad754..5802142abf 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallback.kt @@ -26,7 +26,7 @@ internal class ProcessLifecycleCallback( override fun onStarted() { contextWeakRef.get()?.let { if (WorkManager.isInitialized()) { - cancelUploadWorker(it, internalLogger) + cancelUploadWorker(it, instanceName, internalLogger) } } } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt index aacedcf665..13b8a43f86 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtils.kt @@ -25,10 +25,14 @@ internal const val TAG_DATADOG_UPLOAD = "DatadogBackgroundUpload" internal const val DELAY_MS: Long = 5000 -internal fun cancelUploadWorker(context: Context, internalLogger: InternalLogger) { +internal fun cancelUploadWorker( + context: Context, + instanceName: String, + internalLogger: InternalLogger +) { try { val workManager = WorkManager.getInstance(context) - workManager.cancelAllWorkByTag(TAG_DATADOG_UPLOAD) + workManager.cancelAllWorkByTag("$TAG_DATADOG_UPLOAD/$instanceName") } catch (e: IllegalStateException) { internalLogger.log( InternalLogger.Level.ERROR, @@ -52,7 +56,7 @@ internal fun triggerUploadWorker( .build() val uploadWorkRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java) .setConstraints(constraints) - .addTag(TAG_DATADOG_UPLOAD) + .addTag("$TAG_DATADOG_UPLOAD/$instanceName") .setInitialDelay(DELAY_MS, TimeUnit.MILLISECONDS) .setInputData(Data.Builder().putString(UploadWorker.DATADOG_INSTANCE_NAME, instanceName).build()) .build() diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt index 29b1a1353c..420697cad4 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/lifecycle/ProcessLifecycleCallbackTest.kt @@ -98,7 +98,7 @@ internal class ProcessLifecycleCallbackTest { val workSpec = lastValue.workSpec assertThat(workSpec.workerClassName).isEqualTo(UploadWorker::class.java.canonicalName) assertThat(workSpec.input.getString(UploadWorker.DATADOG_INSTANCE_NAME)).isEqualTo(fakeInstanceName) - assertThat(lastValue.tags).contains(TAG_DATADOG_UPLOAD) + assertThat(lastValue.tags).contains("$TAG_DATADOG_UPLOAD/$fakeInstanceName") } } @@ -132,7 +132,7 @@ internal class ProcessLifecycleCallbackTest { testedCallback.onStarted() // Then - verify(mockWorkManager).cancelAllWorkByTag(TAG_DATADOG_UPLOAD) + verify(mockWorkManager).cancelAllWorkByTag("$TAG_DATADOG_UPLOAD/$fakeInstanceName") } @Test diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt index 53d53a6a0d..a9c43c2aef 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/utils/WorkManagerUtilsTest.kt @@ -85,16 +85,16 @@ internal class WorkManagerUtilsTest { WorkManagerImpl::class.java.setStaticValue("sDefaultInstance", mockWorkManager) // When - cancelUploadWorker(appContext.mockInstance, mockInternalLogger) + cancelUploadWorker(appContext.mockInstance, fakeInstanceName, mockInternalLogger) // Then - verify(mockWorkManager).cancelAllWorkByTag(eq(TAG_DATADOG_UPLOAD)) + verify(mockWorkManager).cancelAllWorkByTag("$TAG_DATADOG_UPLOAD/$fakeInstanceName") } @Test fun `it will handle the cancel exception if WorkManager was not correctly instantiated`() { // When - cancelUploadWorker(appContext.mockInstance, mockInternalLogger) + cancelUploadWorker(appContext.mockInstance, fakeInstanceName, mockInternalLogger) // Then verifyNoInteractions(mockWorkManager) @@ -118,7 +118,7 @@ internal class WorkManagerUtilsTest { val workSpec = lastValue.workSpec assertThat(workSpec.workerClassName).isEqualTo(UploadWorker::class.java.canonicalName) assertThat(workSpec.input.getString(UploadWorker.DATADOG_INSTANCE_NAME)).isEqualTo(fakeInstanceName) - assertThat(lastValue.tags).contains(TAG_DATADOG_UPLOAD) + assertThat(lastValue.tags).contains("$TAG_DATADOG_UPLOAD/$fakeInstanceName") } } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt index 1de4994168..f40b3044d1 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/error/internal/DatadogExceptionHandlerTest.kt @@ -461,7 +461,7 @@ internal class DatadogExceptionHandlerTest { val workSpec = lastValue.workSpec assertThat(workSpec.workerClassName).isEqualTo(UploadWorker::class.java.canonicalName) assertThat(workSpec.input.getString(UploadWorker.DATADOG_INSTANCE_NAME)).isEqualTo(fakeInstanceName) - assertThat(lastValue.tags).contains(TAG_DATADOG_UPLOAD) + assertThat(lastValue.tags).contains("$TAG_DATADOG_UPLOAD/$fakeInstanceName") } } From 315ebd75a67d15d0e47a3e2a2b19d8b03370ac09 Mon Sep 17 00:00:00 2001 From: luyi Date: Tue, 10 Sep 2024 16:39:59 +0200 Subject: [PATCH 017/111] RUM-5188: Add session replay skipped frames count in session ended metrics --- .../android/rum/internal/RumFeature.kt | 6 +++ .../rum/internal/metric/SessionEndedMetric.kt | 15 +++++- .../metric/SessionEndedMetricDispatcher.kt | 4 ++ .../metric/SessionMetricDispatcher.kt | 5 ++ .../internal/monitor/AdvancedRumMonitor.kt | 2 + .../rum/internal/monitor/DatadogRumMonitor.kt | 10 +++- .../metric/RumSessionEndedMetricTest.kt | 30 +++++++++-- .../SessionEndedMetricDispatcherTest.kt | 27 ++++++++++ .../internal/monitor/DatadogRumMonitorTest.kt | 54 ++++++++++++++++--- .../internal/recorder/Debouncer.kt | 23 ++++---- .../recorder/DefaultOnDrawListenerProducer.kt | 2 +- .../listener/WindowsOnDrawListener.kt | 8 +-- .../internal/recorder/DebouncerTest.kt | 33 ++++++++++-- .../listener/WindowsOnDrawListenerTest.kt | 9 +++- 14 files changed, 194 insertions(+), 34 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index c599b09d8c..2cad9be8db 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -277,6 +277,7 @@ internal class RumFeature( TELEMETRY_DEBUG_MESSAGE_TYPE -> logTelemetryDebug(event) MOBILE_METRIC_MESSAGE_TYPE -> logMetric(event) TELEMETRY_CONFIG_MESSAGE_TYPE -> logTelemetryConfiguration(event) + TELEMETRY_SESSION_REPLAY_SKIP_FRAME -> addSessionReplaySkippedFrame() FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE -> { (GlobalRumMonitor.get(sdkCore) as? DatadogRumMonitor)?.let { it.stopKeepAliveCallback() @@ -564,6 +565,10 @@ internal class RumFeature( } } + private fun addSessionReplaySkippedFrame() { + (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor)?.addSessionReplaySkippedFrame() + } + // endregion internal data class Configuration( @@ -600,6 +605,7 @@ internal class RumFeature( internal const val TELEMETRY_DEBUG_MESSAGE_TYPE = "telemetry_debug" internal const val MOBILE_METRIC_MESSAGE_TYPE = "mobile_metric" internal const val TELEMETRY_CONFIG_MESSAGE_TYPE = "telemetry_configuration" + internal const val TELEMETRY_SESSION_REPLAY_SKIP_FRAME = "sr_skipped_frame" internal const val FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE = "flush_and_stop_monitor" internal const val ALL_IN_SAMPLE_RATE: Float = 100f diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetric.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetric.kt index 645344b19b..f8cc60d2ed 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetric.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetric.kt @@ -12,6 +12,7 @@ import com.datadog.android.rum.internal.domain.scope.RumSessionScope import com.datadog.android.rum.internal.domain.scope.RumViewManagerScope import com.datadog.android.rum.model.ViewEvent import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger /** * Metric for rum session ended event. @@ -30,6 +31,8 @@ internal class SessionEndedMetric( private val missedEventCountByType = mutableMapOf() + private var sessionReplaySkippedFramesCount: AtomicInteger = AtomicInteger(0) + private var firstTrackedView: TrackedView? = null private var lastTrackedView: TrackedView? = null private var wasStopped: Boolean = false @@ -69,6 +72,10 @@ internal class SessionEndedMetric( missedEventCountByType[missedEventType] = (missedEventCountByType[missedEventType] ?: 0) + 1 } + fun onSessionReplaySkippedFrameTracked() { + sessionReplaySkippedFramesCount.incrementAndGet() + } + fun toMetricAttributes(ntpOffsetAtEndMs: Long): Map { return mapOf( METRIC_TYPE_KEY to METRIC_TYPE_VALUE, @@ -94,7 +101,8 @@ internal class SessionEndedMetric( SDK_ERRORS_COUNT_KEY to resolveSDKErrorsCountAttributes(), NO_VIEW_EVENTS_COUNT_KEY to resolveNoViewCountsAttributes(), HAS_BACKGROUND_EVENTS_TRACKING_ENABLED_KEY to hasTrackBackgroundEventsEnabled, - NTP_OFFSET_KEY to resolveNtpOffsetAttributes(ntpOffsetAtEnd) + NTP_OFFSET_KEY to resolveNtpOffsetAttributes(ntpOffsetAtEnd), + SESSION_REPLAY_SKIPPED_FRAMES_COUNT to sessionReplaySkippedFramesCount.get() ) } @@ -301,6 +309,11 @@ internal class SessionEndedMetric( * Placeholder of error kind if the attribute is absent. */ internal const val SDK_ERROR_DEFAULT_KIND = "Empty error kind" + + /** + * Key of the counts the total frames skipped in session replay by dynamic optimisation. + */ + internal const val SESSION_REPLAY_SKIPPED_FRAMES_COUNT = "sr_skipped_frames_count" } /** diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcher.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcher.kt index f15bf4f402..31c14f026b 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcher.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcher.kt @@ -72,6 +72,10 @@ internal class SessionEndedMetricDispatcher(private val internalLogger: Internal metricsBySessionId[sessionId]?.onMissedEventTracked(missedEventType) } + override fun onSessionReplaySkippedFrameTracked(sessionId: String) { + metricsBySessionId[sessionId]?.onSessionReplaySkippedFrameTracked() + } + private fun buildSdkErrorTrackError(sessionId: String, errorKind: String?): String { return "Failed to track $errorKind error, session $sessionId has ended" } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionMetricDispatcher.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionMetricDispatcher.kt index c89f453093..8074f25802 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionMetricDispatcher.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/SessionMetricDispatcher.kt @@ -50,4 +50,9 @@ internal interface SessionMetricDispatcher { * Called when a missed event is tracked by this session metric. */ fun onMissedEventTracked(sessionId: String, missedEventType: SessionEndedMetric.MissedEventType) + + /** + * Called when skipped frame is tracked by this session metric. + */ + fun onSessionReplaySkippedFrameTracked(sessionId: String) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt index 272ccaa75c..78fe4105c8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt @@ -29,6 +29,8 @@ internal interface AdvancedRumMonitor : RumMonitor, AdvancedNetworkRumMonitor { fun addLongTask(durationNs: Long, target: String) + fun addSessionReplaySkippedFrame() + fun addCrash( message: String, source: RumErrorSource, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index d433016333..f77b334d45 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -65,7 +65,7 @@ internal class DatadogRumMonitor( private val writer: DataWriter, internal val handler: Handler, internal val telemetryEventHandler: TelemetryEventHandler, - sessionEndedMetricDispatcher: SessionMetricDispatcher, + private val sessionEndedMetricDispatcher: SessionMetricDispatcher, firstPartyHostHeaderTypeResolver: FirstPartyHostHeaderTypeResolver, cpuVitalMonitor: VitalMonitor, memoryVitalMonitor: VitalMonitor, @@ -611,6 +611,14 @@ internal class DatadogRumMonitor( ) } + override fun addSessionReplaySkippedFrame() { + getCurrentSessionId { sessionId -> + sessionId?.let { + sessionEndedMetricDispatcher.onSessionReplaySkippedFrameTracked(it) + } + } + } + override fun sendErrorTelemetryEvent( message: String, throwable: Throwable?, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/RumSessionEndedMetricTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/RumSessionEndedMetricTest.kt index 1790d60931..8effbf858f 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/RumSessionEndedMetricTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/RumSessionEndedMetricTest.kt @@ -437,7 +437,7 @@ class RumSessionEndedMetricTest { } @Test - fun `M have correct 'no_view_events_count' W toMetricAttributes()`( + fun `M have correct no_view_events_count W toMetricAttributes()`( @BoolForgery fakeBackgroundEventTracking: Boolean, @IntForgery(min = 0, max = 5) randomActionCount: Int, @IntForgery(min = 0, max = 5) randomResourceCount: Int, @@ -474,7 +474,7 @@ class RumSessionEndedMetricTest { } @Test - fun `M have correct 'has_background_events_tracking_enabled' W toMetricAttributes()`( + fun `M have correct has_background_events_tracking_enabled W toMetricAttributes()`( @BoolForgery fakeBackgroundEventTracking: Boolean ) { // Given @@ -492,7 +492,7 @@ class RumSessionEndedMetricTest { } @Test - fun `M have correct 'ntp_offset' W toMetricAttributes()`( + fun `M have correct ntp_offset W toMetricAttributes()`( @LongForgery ntpOffsetAtStart: Long, @LongForgery ntpOffsetAtEnd: Long ) { @@ -514,6 +514,30 @@ class RumSessionEndedMetricTest { assertThat(ntpOffset[SessionEndedMetric.NTP_OFFSET_AT_END_KEY]).isEqualTo(ntpOffsetAtEnd) } + @Test + fun `M have correct sr_skipped_frames_count W toMetricAttributes()`( + @IntForgery(min = 0, max = 100) count: Int + ) { + // Given + val sessionEndedMetric = SessionEndedMetric( + fakeSessionId, + fakeStartReason, + fakeNtpOffsetAtStart, + backgroundEventTracking + ) + + // When + repeat(count) { + sessionEndedMetric.onSessionReplaySkippedFrameTracked() + } + val attributes = sessionEndedMetric.toMetricAttributes(fakeNtpOffsetAtEnd) + + // Then + val rse = attributes[SessionEndedMetric.RSE_KEY] as Map<*, *> + val skippedFramesCount = rse[SessionEndedMetric.SESSION_REPLAY_SKIPPED_FRAMES_COUNT] as Int + assertThat(skippedFramesCount).isEqualTo(count) + } + @Test fun `M encode type W creating metric`(@Forgery viewEvent: ViewEvent) { // Given diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcherTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcherTest.kt index c40664f290..937ca6988e 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcherTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/SessionEndedMetricDispatcherTest.kt @@ -12,6 +12,7 @@ import com.datadog.android.rum.utils.forge.Configurator import com.datadog.tools.unit.extensions.TestConfigurationExtension import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -257,6 +258,25 @@ internal class SessionEndedMetricDispatcherTest { assertThat(fakeInternalLogger.getNtpAtEndOffset()).isEqualTo(fakeNtpOffsetAtEnd) } + @Test + fun `M has correct skipped frames count W start metric`( + @StringForgery fakeSessionId: String, + @IntForgery(min = 0, max = 100) skippedFramesCount: Int + ) { + // Given + val dispatcher = SessionEndedMetricDispatcher(fakeInternalLogger) + + // When + dispatcher.startMetric(fakeSessionId, fakeStartReason, fakeNtpOffsetAtStart, backgroundEventTracking) + repeat(skippedFramesCount) { + dispatcher.onSessionReplaySkippedFrameTracked(fakeSessionId) + } + dispatcher.endMetric(fakeSessionId, fakeNtpOffsetAtEnd) + + // Then + assertThat(fakeInternalLogger.getSkippedFramesCount()).isEqualTo(skippedFramesCount) + } + private fun FakeInternalLogger.getNtpAtStartOffset(): Long { return lastMetric?.second?.let { attributes -> val rse = attributes[SessionEndedMetric.RSE_KEY] as Map<*, *> @@ -280,6 +300,13 @@ internal class SessionEndedMetricDispatcherTest { } } + private fun FakeInternalLogger.getSkippedFramesCount(): Int? { + return lastMetric?.second?.let { attributes -> + val rse = attributes[SessionEndedMetric.RSE_KEY] as Map<*, *> + rse[SessionEndedMetric.SESSION_REPLAY_SKIPPED_FRAMES_COUNT] as? Int + } + } + private fun FakeInternalLogger.getMissedTypeCount(missedEventType: SessionEndedMetric.MissedEventType): Int { return lastMetric?.second?.let { attributes -> val rse = attributes[SessionEndedMetric.RSE_KEY] as Map<*, *> diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 5351cc13c3..1708708164 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -80,6 +80,7 @@ import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.same +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions @@ -132,7 +133,7 @@ internal class DatadogRumMonitorTest { lateinit var mockTelemetryEventHandler: TelemetryEventHandler @Mock - lateinit var sessionEndedMetricDispatcher: SessionMetricDispatcher + lateinit var mockSessionEndedMetricDispatcher: SessionMetricDispatcher @Mock lateinit var mockSdkCore: InternalSdkCore @@ -183,7 +184,7 @@ internal class DatadogRumMonitorTest { mockWriter, mockHandler, mockTelemetryEventHandler, - sessionEndedMetricDispatcher, + mockSessionEndedMetricDispatcher, mockResolver, mockCpuVitalMonitor, mockMemoryVitalMonitor, @@ -205,7 +206,7 @@ internal class DatadogRumMonitorTest { mockWriter, mockHandler, mockTelemetryEventHandler, - sessionEndedMetricDispatcher, + mockSessionEndedMetricDispatcher, mockResolver, mockCpuVitalMonitor, mockMemoryVitalMonitor, @@ -264,7 +265,7 @@ internal class DatadogRumMonitorTest { mockWriter, mockHandler, mockTelemetryEventHandler, - sessionEndedMetricDispatcher, + mockSessionEndedMetricDispatcher, mockResolver, mockCpuVitalMonitor, mockMemoryVitalMonitor, @@ -301,7 +302,7 @@ internal class DatadogRumMonitorTest { mockWriter, mockHandler, mockTelemetryEventHandler, - sessionEndedMetricDispatcher, + mockSessionEndedMetricDispatcher, mockResolver, mockCpuVitalMonitor, mockMemoryVitalMonitor, @@ -1617,7 +1618,7 @@ internal class DatadogRumMonitorTest { mockWriter, mockHandler, mockTelemetryEventHandler, - sessionEndedMetricDispatcher, + mockSessionEndedMetricDispatcher, mockResolver, mockCpuVitalMonitor, mockMemoryVitalMonitor, @@ -1662,7 +1663,7 @@ internal class DatadogRumMonitorTest { mockWriter, mockHandler, mockTelemetryEventHandler, - sessionEndedMetricDispatcher, + mockSessionEndedMetricDispatcher, mockResolver, mockCpuVitalMonitor, mockMemoryVitalMonitor, @@ -1694,7 +1695,7 @@ internal class DatadogRumMonitorTest { mockWriter, mockHandler, mockTelemetryEventHandler, - sessionEndedMetricDispatcher, + mockSessionEndedMetricDispatcher, mockResolver, mockCpuVitalMonitor, mockMemoryVitalMonitor, @@ -1871,6 +1872,43 @@ internal class DatadogRumMonitorTest { } } + @Test + fun `M call sessionEndedMetricDispatcher W addSkippedFrame`( + @IntForgery(min = 0, max = 100) count: Int, + @StringForgery(type = StringForgeryType.ASCII) key: String, + @StringForgery name: String + ) { + val attributes = fakeAttributes + (RumAttributes.INTERNAL_TIMESTAMP to fakeTimestamp) + + testedMonitor.startView(key, name, attributes) + // Given + testedMonitor = DatadogRumMonitor( + fakeApplicationId, + mockSdkCore, + 100.0f, + fakeBackgroundTrackingEnabled, + fakeTrackFrustrations, + mockWriter, + mockHandler, + mockTelemetryEventHandler, + mockSessionEndedMetricDispatcher, + mockResolver, + mockCpuVitalMonitor, + mockMemoryVitalMonitor, + mockFrameRateVitalMonitor, + mockSessionListener, + mockExecutorService + ) + testedMonitor.startView(key, name, attributes) + // When + repeat(count) { + testedMonitor.addSessionReplaySkippedFrame() + } + + // Then + verify(mockSessionEndedMetricDispatcher, times(count)).onSessionReplaySkippedFrameTracked(any()) + } + @Test fun `M handle error telemetry event W sendErrorTelemetryEvent() {stack+kind}`( @StringForgery message: String, diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt index 2a13f02d82..24b01f63f1 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt @@ -8,14 +8,15 @@ package com.datadog.android.sessionreplay.internal.recorder import android.os.Handler import android.os.Looper -import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.Feature +import com.datadog.android.api.feature.FeatureSdkCore import java.util.concurrent.TimeUnit internal class Debouncer( private val handler: Handler = Handler(Looper.getMainLooper()), private val maxRecordDelayInNs: Long = MAX_DELAY_THRESHOLD_NS, private val timeBank: TimeBank = RecordingTimeBank(), - private val internalLogger: InternalLogger + private val sdkCore: FeatureSdkCore ) { private var lastTimeRecordWasPerformed = 0L @@ -53,17 +54,16 @@ internal class Debouncer( val endTimeInNano = System.nanoTime() timeBank.consume(endTimeInNano - startTimeInNano) } else { - // TODO RUM-5188 report with telemetry about the frames skipped - internalLogger.log( - InternalLogger.Level.INFO, - InternalLogger.Target.MAINTAINER, - messageBuilder = { - MSG_FRAME_SKIP - } - ) + logSkippedFrame() } } + private fun logSkippedFrame() { + val rumFeature = sdkCore.getFeature(Feature.RUM_FEATURE_NAME) ?: return + val telemetryEvent = mapOf(TYPE_KEY to TYPE_VALUE) + rumFeature.sendEvent(telemetryEvent) + } + companion object { // one frame time private val MAX_DELAY_THRESHOLD_NS: Long = TimeUnit.MILLISECONDS.toNanos(64) @@ -71,6 +71,7 @@ internal class Debouncer( // one frame time internal const val DEBOUNCE_TIME_IN_MS: Long = 64 - private const val MSG_FRAME_SKIP = "Session Replay skipped this recording due to the time limit" + private const val TYPE_VALUE = "sr_skipped_frame" + private const val TYPE_KEY = "type" } } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt index 0338fa8df7..bf71743cc9 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt @@ -32,7 +32,7 @@ internal class DefaultOnDrawListenerProducer( snapshotProducer = snapshotProducer, privacy = privacy, imagePrivacy = imagePrivacy, - internalLogger = sdkCore.internalLogger, + sdkCore = sdkCore, methodCallSamplingRate = MethodCallSamplingRate.LOW.rate ) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt index fbc80ef27c..366bba5843 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt @@ -10,7 +10,7 @@ import android.view.View import android.view.ViewTreeObserver import androidx.annotation.MainThread import androidx.annotation.UiThread -import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.api.feature.measureMethodCallPerf import com.datadog.android.internal.profiler.withinBenchmarkSpan import com.datadog.android.sessionreplay.ImagePrivacy @@ -29,8 +29,8 @@ internal class WindowsOnDrawListener( private val privacy: SessionReplayPrivacy, private val imagePrivacy: ImagePrivacy, private val miscUtils: MiscUtils = MiscUtils, - private val internalLogger: InternalLogger, - private val debouncer: Debouncer = Debouncer(internalLogger = internalLogger), + private val sdkCore: FeatureSdkCore, + private val debouncer: Debouncer = Debouncer(sdkCore = sdkCore), private val methodCallSamplingRate: Float ) : ViewTreeObserver.OnDrawListener { @@ -53,7 +53,7 @@ internal class WindowsOnDrawListener( val systemInformation = miscUtils.resolveSystemInformation(context) val item = recordedDataQueueHandler.addSnapshotItem(systemInformation) ?: return - val nodes = internalLogger.measureMethodCallPerf( + val nodes = sdkCore.internalLogger.measureMethodCallPerf( METHOD_CALL_CALLER_CLASS, METHOD_CALL_CAPTURE_RECORD, methodCallSamplingRate diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt index e5f629ebd1..2dea94b185 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/DebouncerTest.kt @@ -7,7 +7,9 @@ package com.datadog.android.sessionreplay.internal.recorder import android.os.Handler -import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.Feature.Companion.RUM_FEATURE_NAME +import com.datadog.android.api.feature.FeatureScope +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.sessionreplay.forge.ForgeConfigurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -40,19 +42,44 @@ internal class DebouncerTest { lateinit var mockHandler: Handler @Mock - lateinit var mockInternalLogger: InternalLogger + lateinit var mockSdkCore: FeatureSdkCore + + @Mock + lateinit var mockTimeBank: TimeBank + + @Mock + lateinit var mockRumFeature: FeatureScope lateinit var testedDebouncer: Debouncer @BeforeEach fun `set up`() { + whenever(mockTimeBank.updateAndCheck(any())).thenReturn(true) + whenever(mockSdkCore.getFeature(RUM_FEATURE_NAME)).thenReturn(mockRumFeature) testedDebouncer = Debouncer( mockHandler, TEST_MAX_DELAY_THRESHOLD_IN_NS, - internalLogger = mockInternalLogger + sdkCore = mockSdkCore, + timeBank = mockTimeBank ) } + @Test + fun `M send telemetry W frame is skipped by time bank`() { + // Given + whenever(mockTimeBank.updateAndCheck(any())).thenReturn(false) + val fakeRunnable = TestRunnable() + val fakeSecondRunnable = TestRunnable() + testedDebouncer.debounce(fakeRunnable) + Thread.sleep(TimeUnit.NANOSECONDS.toMillis(TEST_MAX_DELAY_THRESHOLD_IN_NS)) + + // When + testedDebouncer.debounce(fakeSecondRunnable) + + // Then + verify(mockRumFeature, times(1)).sendEvent(any()) + } + @Test fun `M delegate to the delayed handler W debounce { first request }`() { // Given diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt index f26d01242a..2a2c590a4a 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListenerTest.kt @@ -12,6 +12,7 @@ import android.content.res.Resources import android.content.res.Resources.Theme import android.view.View import com.datadog.android.api.InternalLogger +import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType import com.datadog.android.sessionreplay.ImagePrivacy @@ -79,6 +80,9 @@ internal class WindowsOnDrawListenerTest { @Mock lateinit var mockInternalLogger: InternalLogger + @Mock + lateinit var mockSdkCore: FeatureSdkCore + @Mock lateinit var mockPerformanceMetric: PerformanceMetric @@ -118,6 +122,7 @@ internal class WindowsOnDrawListenerTest { @BeforeEach fun `set up`(forge: Forge) { + whenever(mockSdkCore.internalLogger).thenReturn(mockInternalLogger) whenever(mockMiscUtils.resolveSystemInformation(mockContext)) .thenReturn(fakeSystemInformation) fakeMockedDecorViews = forge.aMockedDecorViewList().onEach { @@ -161,7 +166,7 @@ internal class WindowsOnDrawListenerTest { imagePrivacy = fakeImagePrivacy, debouncer = mockDebouncer, miscUtils = mockMiscUtils, - internalLogger = mockInternalLogger, + sdkCore = mockSdkCore, methodCallSamplingRate = fakeMethodCallSamplingRate ) } @@ -213,7 +218,7 @@ internal class WindowsOnDrawListenerTest { imagePrivacy = fakeImagePrivacy, debouncer = mockDebouncer, miscUtils = mockMiscUtils, - internalLogger = mockInternalLogger, + sdkCore = mockSdkCore, methodCallSamplingRate = fakeMethodCallSamplingRate ) testedListener.onDraw() From daaa233785f292ee285725476270632f665a5c00 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Thu, 12 Sep 2024 14:58:16 +0200 Subject: [PATCH 018/111] Explicitly set antlr-runtime transitive dependency version --- buildSrc/build.gradle.kts | 1 + gradle/libs.versions.toml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 76991d4eb0..d8a1c375d4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { // check api surface implementation(libs.kotlinGrammarParser) + implementation(libs.kotlinAntlrRuntime) // JsonSchema 2 Poko implementation(libs.gson) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84ec961c34..a80425456d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,6 +68,10 @@ nexusPublishGradlePlugin = "1.1.0" kotlinPoet = "1.14.2" kotlinGrammarParser = "0.1.0" +# version d4384e4d90 of com.github.drieks.antlr-kotlin:antlr-kotlin-runtime +# referenced by com.github.kotlinx.ast:grammar-kotlin-parser-antlr-kotlin-jvm cannot be found +# so explicitly overriding with the version which can be found +kotlinAntlrRuntime = "v0.1.0" jsonSchemaValidator = "1.12.1" binaryCompatibility = "0.13.2" dependencyLicense = "0.3" @@ -203,6 +207,7 @@ kotlinPoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinPoet" } kotlinPoetKsp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinPoet" } kotlinSP = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "kotlinSP" } kotlinGrammarParser = { module = "com.github.kotlinx.ast:grammar-kotlin-parser-antlr-kotlin-jvm", version.ref = "kotlinGrammarParser" } +kotlinAntlrRuntime = { module = "com.github.drieks.antlr-kotlin:antlr-kotlin-runtime", version.ref = "kotlinAntlrRuntime" } jsonSchemaValidator = { module = "com.github.everit-org.json-schema:org.everit.json.schema", version.ref = "jsonSchemaValidator" } # Integrations From d059818858f1d926d23af6097ac9c7e064cd73fa Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Thu, 12 Sep 2024 12:19:17 +0200 Subject: [PATCH 019/111] Update RUM Error event schema --- features/dd-sdk-android-rum/api/apiSurface | 2 ++ features/dd-sdk-android-rum/api/dd-sdk-android-rum.api | 2 ++ features/dd-sdk-android-rum/src/main/json/rum/error-schema.json | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index 417417fc22..ed655c0ffe 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -791,6 +791,8 @@ data class com.datadog.android.rum.model.ErrorEvent - ANR - APP_HANG - EXCEPTION + - WATCHDOG_TERMINATION + - MEMORY_WARNING fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Category diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 7dfe4e03b7..9f2181d57a 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -1340,6 +1340,8 @@ public final class com/datadog/android/rum/model/ErrorEvent$Category : java/lang public static final field APP_HANG Lcom/datadog/android/rum/model/ErrorEvent$Category; public static final field Companion Lcom/datadog/android/rum/model/ErrorEvent$Category$Companion; public static final field EXCEPTION Lcom/datadog/android/rum/model/ErrorEvent$Category; + public static final field MEMORY_WARNING Lcom/datadog/android/rum/model/ErrorEvent$Category; + public static final field WATCHDOG_TERMINATION Lcom/datadog/android/rum/model/ErrorEvent$Category; public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Category; public final fun toJson ()Lcom/google/gson/JsonElement; public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/rum/model/ErrorEvent$Category; diff --git a/features/dd-sdk-android-rum/src/main/json/rum/error-schema.json b/features/dd-sdk-android-rum/src/main/json/rum/error-schema.json index 96934b2dba..3350fdb70e 100644 --- a/features/dd-sdk-android-rum/src/main/json/rum/error-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/rum/error-schema.json @@ -101,7 +101,7 @@ "category": { "type": "string", "description": "The specific category of the error. It provides a high-level grouping for different types of errors.", - "enum": ["ANR", "App Hang", "Exception"], + "enum": ["ANR", "App Hang", "Exception", "Watchdog Termination", "Memory Warning"], "readOnly": true }, "handling": { From bb8902227006806d95bbb3af8ad548a9aa2827ba Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Thu, 5 Sep 2024 15:51:18 +0200 Subject: [PATCH 020/111] Have the NoOpGenerator support the RequiredOptIn functions --- .../noopfactory/NoOpFactorySymbolProcessor.kt | 27 ++++++++++++++++++- .../noopfactory/NoOpFactoryProviderTest.kt | 7 +++-- .../gen/NoOpExperimentalInterface.kt | 12 +++++++++ .../src/test/resources/src/ExperimentalApi.kt | 17 ++++++++++++ .../resources/src/ExperimentalInterface.kt | 9 +++++++ 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 tools/noopfactory/src/test/resources/gen/NoOpExperimentalInterface.kt create mode 100644 tools/noopfactory/src/test/resources/src/ExperimentalApi.kt create mode 100644 tools/noopfactory/src/test/resources/src/ExperimentalInterface.kt diff --git a/tools/noopfactory/src/main/kotlin/com/datadog/tools/noopfactory/NoOpFactorySymbolProcessor.kt b/tools/noopfactory/src/main/kotlin/com/datadog/tools/noopfactory/NoOpFactorySymbolProcessor.kt index a18df01520..f5326350a0 100644 --- a/tools/noopfactory/src/main/kotlin/com/datadog/tools/noopfactory/NoOpFactorySymbolProcessor.kt +++ b/tools/noopfactory/src/main/kotlin/com/datadog/tools/noopfactory/NoOpFactorySymbolProcessor.kt @@ -59,6 +59,7 @@ class NoOpFactorySymbolProcessor( ) : SymbolProcessor { private var invoked = false + private var requireOptInAnnotations: Set = emptySet() /** @inheritdoc */ override fun process(resolver: Resolver): List { @@ -66,7 +67,7 @@ class NoOpFactorySymbolProcessor( logger.info("Already invoked, ignoring") return emptyList() } - + resolveRequireOptInAnnotations(resolver) val result = mutableListOf() resolver.getSymbolsWithAnnotation(NoOpImplementation::class.java.canonicalName) .filterIsInstance() @@ -86,6 +87,18 @@ class NoOpFactorySymbolProcessor( return result } + private fun resolveRequireOptInAnnotations(resolver: Resolver) { + requireOptInAnnotations = resolver + .getSymbolsWithAnnotation("kotlin.RequiresOptIn") + .filterIsInstance() + .filter { it.classKind == ClassKind.ANNOTATION_CLASS } + .map { + logger.info("Found RequiresOptIn annotation: ${it.qualifiedName?.asString()}") + it.asType(emptyList()) + } + .toSet() + } + // region Internal Generation @OptIn(KspExperimental::class) @@ -325,6 +338,7 @@ class NoOpFactorySymbolProcessor( val deprecatedAnnotation = functionDeclaration .getAnnotationsByType(Deprecated::class) .firstOrNull() + if (deprecatedAnnotation != null) { funSpecBuilder.addAnnotation( AnnotationSpec.builder(Deprecated::class) @@ -333,6 +347,17 @@ class NoOpFactorySymbolProcessor( ) } + // check if the function has any RequiresOptIn annotations and add OptIn annotation for the override function + functionDeclaration + .annotations + .filter { it.annotationType.resolve() in requireOptInAnnotations } + .forEach { + funSpecBuilder.addAnnotation( + AnnotationSpec.builder(ClassName("kotlin", "OptIn")) + .addMember("%T::class", it.annotationType.resolve().toTypeName()) + .build() + ) + } val returnType = functionDeclaration.returnType?.resolve() if (returnType != null) { generateFunctionReturnStatement(funSpecBuilder, returnType, params, typeParamResolver) diff --git a/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt b/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt index b66e0420eb..237f792bf9 100644 --- a/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt +++ b/tools/noopfactory/src/test/kotlin/com/datadog/tools/noopfactory/NoOpFactoryProviderTest.kt @@ -26,17 +26,20 @@ internal class NoOpFactoryProviderTest { "AnyGenericInterface.kt:NoOpAnyGenericInterface.kt", "EnumInterface.kt:NoOpEnumInterface.kt", "OverloadedInterface.kt:NoOpOverloadedInterface.kt", - "PublicImplementation.kt:NoOpPublicImplementation.kt" + "PublicImplementation.kt:NoOpPublicImplementation.kt", + "ExperimentalInterface.kt:NoOpExperimentalInterface.kt" ] ) fun `implement a NoOp class from interface`(srcFileName: String, genFileName: String) { val srcFile = File(javaClass.getResource("/src/$srcFileName")!!.file) + val experimentalApiAnnotationFile = File(javaClass.getResource("/src/ExperimentalApi.kt")!!.file) val genFile = File(javaClass.getResource("/gen/$genFileName")!!.file) val kotlinSource = SourceFile.fromPath(srcFile) + val experimentalApiAnnotationSource = SourceFile.fromPath(experimentalApiAnnotationFile) val result = KotlinCompilation().apply { inheritClassPath = true - sources = listOf(kotlinSource) + sources = listOf(kotlinSource, experimentalApiAnnotationSource) symbolProcessorProviders = listOf(NoOpFactoryProvider()) }.compile() diff --git a/tools/noopfactory/src/test/resources/gen/NoOpExperimentalInterface.kt b/tools/noopfactory/src/test/resources/gen/NoOpExperimentalInterface.kt new file mode 100644 index 0000000000..afa71cdf5b --- /dev/null +++ b/tools/noopfactory/src/test/resources/gen/NoOpExperimentalInterface.kt @@ -0,0 +1,12 @@ +@file:Suppress("ktlint") + +package com.example + +import kotlin.OptIn +import kotlin.Suppress + +internal class NoOpExperimentalInterface : ExperimentalInterface { + @OptIn(ExperimentalApi::class) + override fun doSomethingExperimental() { + } +} diff --git a/tools/noopfactory/src/test/resources/src/ExperimentalApi.kt b/tools/noopfactory/src/test/resources/src/ExperimentalApi.kt new file mode 100644 index 0000000000..7ea844e292 --- /dev/null +++ b/tools/noopfactory/src/test/resources/src/ExperimentalApi.kt @@ -0,0 +1,17 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.example + +/** + * Marker for the experimental RUM API. + */ +@RequiresOptIn( + message = "This is an experimental RUM API." + + " It may change in the future.", + level = RequiresOptIn.Level.WARNING +) +annotation class ExperimentalApi diff --git a/tools/noopfactory/src/test/resources/src/ExperimentalInterface.kt b/tools/noopfactory/src/test/resources/src/ExperimentalInterface.kt new file mode 100644 index 0000000000..003b232c8c --- /dev/null +++ b/tools/noopfactory/src/test/resources/src/ExperimentalInterface.kt @@ -0,0 +1,9 @@ +package com.example + +import com.datadog.tools.annotation.NoOpImplementation + +@NoOpImplementation +interface ExperimentalInterface { + @ExperimentalApi + fun doSomethingExperimental() +} From 8f5c1b269278c197a7ab4b33b41eaf4ddaa4775b Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Thu, 5 Sep 2024 15:52:11 +0200 Subject: [PATCH 021/111] Introduce the RumMonitor#addViewLoadingTimeApi --- features/dd-sdk-android-rum/api/apiSurface | 2 + .../api/dd-sdk-android-rum.api | 4 + .../datadog/android/rum/ExperimentalRumApi.kt | 18 ++ .../com/datadog/android/rum/RumMonitor.kt | 9 + .../rum/internal/domain/scope/RumRawEvent.kt | 4 + .../rum/internal/domain/scope/RumViewScope.kt | 13 +- .../rum/internal/monitor/DatadogRumMonitor.kt | 6 + .../internal/domain/scope/RumViewScopeTest.kt | 159 ++++++++++++++++++ .../internal/monitor/DatadogRumMonitorTest.kt | 13 ++ 9 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/ExperimentalRumApi.kt diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index ed655c0ffe..074d735e04 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -6,6 +6,7 @@ class com.datadog.android.rum.DdRumContentProvider : android.content.ContentProv override fun insert(android.net.Uri, android.content.ContentValues?): android.net.Uri? override fun delete(android.net.Uri, String?, Array?): Int override fun update(android.net.Uri, android.content.ContentValues?, String?, Array?): Int +annotation com.datadog.android.rum.ExperimentalRumApi typealias GlobalRum = GlobalRumMonitor object com.datadog.android.rum.GlobalRumMonitor fun isRegistered(com.datadog.android.api.SdkCore = Datadog.getInstance()): Boolean @@ -111,6 +112,7 @@ interface com.datadog.android.rum.RumMonitor fun getAttributes(): Map fun clearAttributes() fun stopSession() + fun addViewLoadingTime() var debug: Boolean fun _getInternal(): _RumInternalProxy? enum com.datadog.android.rum.RumPerformanceMetric diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 9f2181d57a..c6b78820f7 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -13,6 +13,9 @@ public final class com/datadog/android/rum/DdRumContentProvider : android/conten public fun update (Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I } +public abstract interface annotation class com/datadog/android/rum/ExperimentalRumApi : java/lang/annotation/Annotation { +} + public final class com/datadog/android/rum/GlobalRumMonitor { public static final field INSTANCE Lcom/datadog/android/rum/GlobalRumMonitor; public static final fun get ()Lcom/datadog/android/rum/RumMonitor; @@ -141,6 +144,7 @@ public abstract interface class com/datadog/android/rum/RumMonitor { public abstract fun addFeatureFlagEvaluation (Ljava/lang/String;Ljava/lang/Object;)V public abstract fun addFeatureFlagEvaluations (Ljava/util/Map;)V public abstract fun addTiming (Ljava/lang/String;)V + public abstract fun addViewLoadingTime ()V public abstract fun clearAttributes ()V public abstract fun getAttributes ()Ljava/util/Map; public abstract fun getCurrentSessionId (Lkotlin/jvm/functions/Function1;)V diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/ExperimentalRumApi.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/ExperimentalRumApi.kt new file mode 100644 index 0000000000..4ba592871c --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/ExperimentalRumApi.kt @@ -0,0 +1,18 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum + +/** + * Marker for the experimental RUM API. + */ +@RequiresOptIn( + message = "This is an experimental RUM API." + + " It may change in the future.", + level = RequiresOptIn.Level.WARNING +) +@Retention(AnnotationRetention.BINARY) +annotation class ExperimentalRumApi diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt index 279ef49d89..cb96078774 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt @@ -323,6 +323,15 @@ interface RumMonitor { */ fun stopSession() + /** + * Adds view loading time to current RUM view based on the time elapsed since the view was started. + * This method should be called only once per view. + * If the view is not started, this method does nothing. + * If the view is not active(stopped), this method does nothing. + */ + @ExperimentalRumApi + fun addViewLoadingTime() + /** * Utility setting to inspect the active RUM View. * If set, a debugging outline will be displayed on top of the application, describing the name diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt index 97f4075646..7d61d3b45e 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt @@ -173,6 +173,10 @@ internal sealed class RumRawEvent { override val eventTime: Time = Time() ) : RumRawEvent() + internal data class AddViewLoadingTime( + override val eventTime: Time = Time() + ) : RumRawEvent() + internal data class AddLongTask( val durationNs: Long, val target: String, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 79eef30a62..c4bfc98112 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -79,6 +79,7 @@ internal open class RumViewScope( private val oldViewIds = mutableSetOf() private val startedNanos: Long = eventTime.nanoTime + internal var viewLoadingTime: Long? = null internal val serverTimeOffsetInMs = sdkCore.time.serverTimeOffsetMs internal val eventTimestamp = eventTime.timestamp + serverTimeOffsetInMs @@ -194,6 +195,7 @@ internal open class RumViewScope( is RumRawEvent.StopSession -> onStopSession(event, writer) is RumRawEvent.UpdatePerformanceMetric -> onUpdatePerformanceMetric(event) + is RumRawEvent.AddViewLoadingTime -> onAddViewLoadingTime(event, writer) else -> delegateEventToChildren(event, writer) } @@ -236,6 +238,14 @@ internal open class RumViewScope( // region Internal + @WorkerThread + private fun onAddViewLoadingTime(event: RumRawEvent.AddViewLoadingTime, writer: DataWriter) { + if (stopped) return + + viewLoadingTime = event.eventTime.nanoTime - startedNanos + sendViewUpdate(event, writer) + } + @WorkerThread private fun onStartView( event: RumRawEvent.StartView, @@ -834,7 +844,8 @@ internal open class RumViewScope( frustration = ViewEvent.Frustration(eventFrustrationCount.toLong()), flutterBuildTime = eventFlutterBuildTime, flutterRasterTime = eventFlutterRasterTime, - jsRefreshRate = eventJsRefreshRate + jsRefreshRate = eventJsRefreshRate, + loadingTime = viewLoadingTime ), usr = if (user.hasUserData()) { ViewEvent.Usr( diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index d433016333..1abea4f039 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -17,6 +17,7 @@ import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.utils.loggableStackTrace import com.datadog.android.core.internal.utils.submitSafe import com.datadog.android.rum.DdRumContentProvider +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource @@ -539,6 +540,11 @@ internal class DatadogRumMonitor( ) } + @ExperimentalRumApi + override fun addViewLoadingTime() { + handleEvent(RumRawEvent.AddViewLoadingTime()) + } + override fun addLongTask(durationNs: Long, target: String) { handleEvent( RumRawEvent.AddLongTask(durationNs, target) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index 68776d2092..cb775aed1e 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -6960,6 +6960,165 @@ internal class RumViewScopeTest { // endregion + // region View Loading Time + + @Test + fun `M send event with view loading time W handleEvent(AddViewLoadingTime) on active view`() { + // Given + val viewLoadingTimeEvent = RumRawEvent.AddViewLoadingTime() + val expectedViewLoadingTime = viewLoadingTimeEvent.eventTime.nanoTime - fakeEventTime.nanoTime + + // When + testedScope.handleEvent( + viewLoadingTimeEvent, + mockWriter + ) + + // Then + argumentCaptor { + verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.DEFAULT)) + assertThat(lastValue) + .apply { + hasTimestamp(resolveExpectedTimestamp(fakeEventTime.timestamp)) + hasName(fakeKey.name) + hasUrl(fakeUrl) + hasDurationGreaterThan(1) + hasLoadingType(null) + hasVersion(2) + hasErrorCount(0) + hasResourceCount(0) + hasActionCount(0) + hasFrustrationCount(0) + hasLongTaskCount(0) + hasFrozenFrameCount(0) + hasCpuMetric(null) + hasMemoryMetric(null, null) + hasRefreshRateMetric(null, null) + isActive(true) + isSlowRendered(false) + hasUserInfo(fakeDatadogContext.userInfo) + hasViewId(testedScope.viewId) + hasApplicationId(fakeParentContext.applicationId) + hasSessionId(fakeParentContext.sessionId) + hasUserSession() + hasNoSyntheticsTest() + hasStartReason(fakeParentContext.sessionStartReason) + hasReplay(fakeHasReplay) + hasReplayStats(fakeReplayStats) + hasSource(fakeSourceViewEvent) + hasLoadingTime(expectedViewLoadingTime) + hasDeviceInfo( + fakeDatadogContext.deviceInfo.deviceName, + fakeDatadogContext.deviceInfo.deviceModel, + fakeDatadogContext.deviceInfo.deviceBrand, + fakeDatadogContext.deviceInfo.deviceType.toViewSchemaType(), + fakeDatadogContext.deviceInfo.architecture + ) + hasOsInfo( + fakeDatadogContext.deviceInfo.osName, + fakeDatadogContext.deviceInfo.osVersion, + fakeDatadogContext.deviceInfo.osMajorVersion + ) + hasConnectivityInfo(fakeDatadogContext.networkInfo) + hasServiceName(fakeDatadogContext.service) + hasVersion(fakeDatadogContext.version) + hasSessionActive(fakeParentContext.isSessionActive) + hasSampleRate(fakeSampleRate) + } + } + verifyNoMoreInteractions(mockWriter) + } + + @Test + fun `M send event with view loading time W handleEvent(AddViewLoadingTime) on active view called multiple times`( + forge: Forge + ) { + // Given + val viewLoadingTimeEvents = forge.aList { RumRawEvent.AddViewLoadingTime() } + val expectedViewLoadingTime = viewLoadingTimeEvents.last().eventTime.nanoTime - fakeEventTime.nanoTime + + // When + viewLoadingTimeEvents.forEach { + testedScope.handleEvent( + it, + mockWriter + ) + } + + // Then + argumentCaptor { + verify(mockWriter, times(viewLoadingTimeEvents.size)) + .write(eq(mockEventBatchWriter), capture(), eq(EventType.DEFAULT)) + assertThat(lastValue) + .apply { + hasTimestamp(resolveExpectedTimestamp(fakeEventTime.timestamp)) + hasName(fakeKey.name) + hasUrl(fakeUrl) + hasDurationGreaterThan(1) + hasLoadingType(null) + hasVersion(2) + hasErrorCount(0) + hasResourceCount(0) + hasActionCount(0) + hasFrustrationCount(0) + hasLongTaskCount(0) + hasFrozenFrameCount(0) + hasCpuMetric(null) + hasMemoryMetric(null, null) + hasRefreshRateMetric(null, null) + isActive(true) + isSlowRendered(false) + hasUserInfo(fakeDatadogContext.userInfo) + hasViewId(testedScope.viewId) + hasApplicationId(fakeParentContext.applicationId) + hasSessionId(fakeParentContext.sessionId) + hasUserSession() + hasNoSyntheticsTest() + hasStartReason(fakeParentContext.sessionStartReason) + hasReplay(fakeHasReplay) + hasReplayStats(fakeReplayStats) + hasSource(fakeSourceViewEvent) + hasLoadingTime(expectedViewLoadingTime) + hasDeviceInfo( + fakeDatadogContext.deviceInfo.deviceName, + fakeDatadogContext.deviceInfo.deviceModel, + fakeDatadogContext.deviceInfo.deviceBrand, + fakeDatadogContext.deviceInfo.deviceType.toViewSchemaType(), + fakeDatadogContext.deviceInfo.architecture + ) + hasOsInfo( + fakeDatadogContext.deviceInfo.osName, + fakeDatadogContext.deviceInfo.osVersion, + fakeDatadogContext.deviceInfo.osMajorVersion + ) + hasConnectivityInfo(fakeDatadogContext.networkInfo) + hasServiceName(fakeDatadogContext.service) + hasVersion(fakeDatadogContext.version) + hasSessionActive(fakeParentContext.isSessionActive) + hasSampleRate(fakeSampleRate) + } + } + verifyNoMoreInteractions(mockWriter) + } + + @Test + fun `M not update the view loading time W handleEvent(AddViewLoadingTime) on stopped view`() { + // Given + testedScope.stopped = true + + // When + testedScope.handleEvent( + RumRawEvent.AddViewLoadingTime(), + mockWriter + ) + + // Then + assertThat(testedScope.viewLoadingTime).isNull() + verifyNoInteractions(mockWriter) + } + + // endregion + // region Vitals @Test diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 5351cc13c3..113737743e 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -874,6 +874,19 @@ internal class DatadogRumMonitorTest { verifyNoMoreInteractions(mockScope, mockWriter) } + @Test + fun `M delegate event to rootScope W addViewLoadTime()`() { + testedMonitor.addViewLoadingTime() + Thread.sleep(PROCESSING_DELAY) + + argumentCaptor { + verify(mockScope).handleEvent(capture(), same(mockWriter)) + val event = firstValue + check(event is RumRawEvent.AddViewLoadingTime) + } + verifyNoMoreInteractions(mockScope, mockWriter) + } + @Test fun `M delegate event to rootScope on current thread W addCrash()`( @StringForgery message: String, From aefa556d31085fae9bade55fe3af7bdb5c460331 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Thu, 5 Sep 2024 15:52:35 +0200 Subject: [PATCH 022/111] Use the RumMonitor#addViewLoadingTIme API into the sample app --- .../internal/domain/scope/RumViewScopeTest.kt | 2 +- .../internal/monitor/DatadogRumMonitorTest.kt | 2 ++ .../sample/SynchronousLoadedFragment.kt | 25 +++++++++++++++ .../android/sample/ViewPagerFragment.kt | 2 +- .../android/sample/about/AboutFragment.kt | 4 +-- .../sample/compose/JetpackComposeActivity.kt | 5 ++- .../sample/compose/NavigationSample.kt | 32 +++++++++++++++++++ .../android/sample/crash/CrashFragment.kt | 4 +-- .../sample/datalist/DataListFragment.kt | 3 ++ .../android/sample/gdpr/GdprDialogFragment.kt | 8 +++++ .../android/sample/home/HomeFragment.kt | 4 +-- .../android/sample/logs/LogsFragment.kt | 4 +-- .../android/sample/picture/PictureFragment.kt | 4 +-- .../sessionreplay/DifferentFontsFragment.kt | 4 +-- .../DropDownSwitchersFragment.kt | 4 +-- .../sessionreplay/ImageComponentsFragment.kt | 17 ++++++++++ .../sessionreplay/ImageScalingFragment.kt | 4 +-- .../sessionreplay/PickerComponentsFragment.kt | 4 +-- .../PrivacySensitiveTextComponentsFragment.kt | 4 +-- .../sessionreplay/RadioCheckBoxesFragment.kt | 4 +-- .../sessionreplay/SessionReplayFragment.kt | 4 +-- .../sessionreplay/SlidersSteppersFragment.kt | 4 +-- .../TextViewComponentsFragment.kt | 4 +-- .../sessionreplay/UnsupportedViewsFragment.kt | 4 +-- .../sessionreplay/WebViewRecordFragment.kt | 16 +++++++++- .../sample/traces/OtelTracesFragment.kt | 4 +-- .../android/sample/traces/TracesFragment.kt | 4 +-- .../android/sample/user/UserFragment.kt | 4 +-- .../sample/viewpager/PagerChildFragment.kt | 16 +++++++++- .../android/sample/vitals/VitalsFragment.kt | 4 +-- .../android/sample/webview/WebFragment.kt | 16 +++++++++- 31 files changed, 176 insertions(+), 44 deletions(-) create mode 100644 sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index cb775aed1e..aa5d6c379a 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -7056,7 +7056,7 @@ internal class RumViewScopeTest { hasUrl(fakeUrl) hasDurationGreaterThan(1) hasLoadingType(null) - hasVersion(2) + hasVersion((viewLoadingTimeEvents.size + 1).toLong()) hasErrorCount(0) hasResourceCount(0) hasActionCount(0) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 113737743e..4c1fe84d02 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -18,6 +18,7 @@ import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.utils.loggableStackTrace import com.datadog.android.rum.DdRumContentProvider +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource @@ -875,6 +876,7 @@ internal class DatadogRumMonitorTest { } @Test + @OptIn(ExperimentalRumApi::class) fun `M delegate event to rootScope W addViewLoadTime()`() { testedMonitor.addViewLoadingTime() Thread.sleep(PROCESSING_DELAY) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt new file mode 100644 index 0000000000..307fb01036 --- /dev/null +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt @@ -0,0 +1,25 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.sample + +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment +import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.GlobalRumMonitor + +internal open class SynchronousLoadedFragment : Fragment { + + constructor() : super() + + constructor(@LayoutRes contentLayoutId: Int) : super(contentLayoutId) + + @OptIn(ExperimentalRumApi::class) + override fun onResume() { + super.onResume() + GlobalRumMonitor.get().addViewLoadingTime() + } +} diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt index b755c5c466..5ad121ef5e 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt @@ -26,7 +26,7 @@ import com.google.android.material.tabs.TabLayoutMediator /** * An [Activity] with a [ViewPager] holding multiple fragments. */ -class ViewPagerFragment : Fragment() { +class ViewPagerFragment : SynchronousLoadedFragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt index 4a00488eef..93afc0d04f 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt @@ -11,12 +11,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment internal class AboutFragment : - Fragment() { + SynchronousLoadedFragment() { private lateinit var viewModel: AboutViewModel private lateinit var aboutText: TextView diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt index af275c0304..6e622a0f29 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.google.accompanist.appcompattheme.AppCompatTheme import com.google.accompanist.pager.ExperimentalPagerApi @@ -38,7 +39,7 @@ import kotlinx.coroutines.launch class JetpackComposeActivity : AppCompatActivity() { @Suppress("LongMethod") - @OptIn(ExperimentalPagerApi::class) + @OptIn(ExperimentalPagerApi::class, ExperimentalRumApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { @@ -57,6 +58,7 @@ class JetpackComposeActivity : AppCompatActivity() { val screen = pages[pagerState.currentPage].trackingName if (event == Lifecycle.Event.ON_RESUME) { rumMonitor.startView(screen, screen) + rumMonitor.addViewLoadingTime() } else if (event == Lifecycle.Event.ON_PAUSE) { rumMonitor.stopView(screen) } @@ -76,6 +78,7 @@ class JetpackComposeActivity : AppCompatActivity() { .collect { page -> val screen = pages[page].trackingName GlobalRumMonitor.get().startView(screen, screen) + GlobalRumMonitor.get().addViewLoadingTime() } } diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt index 7e80e8a97e..79aa3df060 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt @@ -13,12 +13,17 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost @@ -28,6 +33,8 @@ import androidx.navigation.navArgument import com.datadog.android.compose.ExperimentalTrackingApi import com.datadog.android.compose.NavigationViewTrackingEffect import com.datadog.android.compose.trackClick +import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.tracking.AcceptAllNavDestinations import java.lang.IllegalArgumentException import kotlin.random.Random @@ -35,6 +42,12 @@ import kotlin.random.Random @OptIn(ExperimentalTrackingApi::class) @Composable internal fun NavigationSampleView() { + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner) { + val observer = DatadogLifecycleObserver() + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } + } val navController = rememberNavController().apply { NavigationViewTrackingEffect( navController = this, @@ -57,6 +70,13 @@ internal fun SimpleView( @PreviewParameter(provider = SimpleViewIdPreviewProvider::class) viewId: String, onNavigate: () -> Unit = {} ) { + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner) { + val observer = DatadogLifecycleObserver() + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } + } + Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, @@ -129,3 +149,15 @@ internal sealed class Screen( internal fun oneOf(vararg items: T): T { return items[Random.nextInt(items.size)] } + +private class DatadogLifecycleObserver : LifecycleEventObserver { + private var viewIsLoaded = false + + @OptIn(ExperimentalRumApi::class) + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (event == Lifecycle.Event.ON_RESUME && !viewIsLoaded) { + GlobalRumMonitor.get().addViewLoadingTime() + viewIsLoaded = true + } + } +} diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt index f95bba498f..a1d95f13ba 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt @@ -15,13 +15,13 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import androidx.appcompat.widget.AppCompatSpinner -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment @Suppress("MagicNumber") internal class CrashFragment : - Fragment(), + SynchronousLoadedFragment(), View.OnClickListener { private lateinit var viewModel: CrashViewModel diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt index c61deba301..29b5e8a948 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.datadog.android.Datadog +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.R import com.datadog.android.sample.SampleApplication @@ -42,6 +43,7 @@ internal class DataListFragment : Fragment() { setHasOptionsMenu(true) } + @OptIn(ExperimentalRumApi::class) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -63,6 +65,7 @@ internal class DataListFragment : Fragment() { is DataListViewModel.UIResponse.Success -> { if (!firstDataWasLoaded) { GlobalRumMonitor.get(Datadog.getInstance()).addTiming("logs_data_loaded") + GlobalRumMonitor.get().addViewLoadingTime() firstDataWasLoaded = true } adapter.updateData(it.data) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt index 688d38a33e..b5230d58d0 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt @@ -15,6 +15,8 @@ import androidx.annotation.IdRes import androidx.fragment.app.DialogFragment import com.datadog.android.Datadog import com.datadog.android.privacy.TrackingConsent +import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.Preferences import com.datadog.android.sample.R import com.datadog.android.sample.TrackingConsentChangeListener @@ -47,6 +49,12 @@ internal class GdprDialogFragment : DialogFragment() { return rootView } + @OptIn(ExperimentalRumApi::class) + override fun onResume() { + super.onResume() + GlobalRumMonitor.get().addViewLoadingTime() + } + @IdRes private fun resolveButtonIdFromConsent(trackingConsent: TrackingConsent): Int { return when (trackingConsent) { diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt index bf4cb1fc84..e9772d4450 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt @@ -12,14 +12,14 @@ import android.view.ViewGroup import android.widget.Button import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.children -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment.Companion.findNavController import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment internal class HomeFragment : - Fragment(), + SynchronousLoadedFragment(), View.OnClickListener { private lateinit var viewModel: HomeViewModel diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt index b3a9add74e..680b32af91 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt @@ -13,15 +13,15 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.EditText import android.widget.Spinner -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.log.Logger import com.datadog.android.sample.BuildConfig import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment @Suppress("DEPRECATION") internal class LogsFragment : - Fragment(), + SynchronousLoadedFragment(), View.OnClickListener { private var interactionsCount = 0 diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt index 69e94bcdbd..b207cbf347 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt @@ -13,13 +13,13 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ImageView -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.datadog.android.sample.Preferences import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment internal class PictureFragment : - Fragment(), View.OnClickListener { + SynchronousLoadedFragment(), View.OnClickListener { private lateinit var picture: ImageView private lateinit var rootView: View diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt index c3c059857d..2360e8339f 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt @@ -6,7 +6,7 @@ package com.datadog.android.sample.sessionreplay -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment -internal class DifferentFontsFragment : Fragment(R.layout.fragment_different_fonts) +internal class DifferentFontsFragment : SynchronousLoadedFragment(R.layout.fragment_different_fonts) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt index 4e6614b42d..001d9f8dc7 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt @@ -13,10 +13,10 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.Spinner import androidx.appcompat.widget.SwitchCompat -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment -internal class DropDownSwitchersFragment : Fragment() { +internal class DropDownSwitchersFragment : SynchronousLoadedFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val root = inflater.inflate(R.layout.fragment_drop_down_switchers_components, container, false) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt index 9b7c269416..b1040039ac 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt @@ -18,7 +18,10 @@ import android.widget.TextView import androidx.appcompat.widget.AppCompatImageButton import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.R +import java.util.concurrent.atomic.AtomicInteger internal interface ImageLoadedCallback { fun onImageLoaded(resource: Drawable) @@ -32,6 +35,9 @@ internal class ImageComponentsFragment : Fragment() { private lateinit var imageButtonRemote: ImageButton private lateinit var appCompatButtonRemote: AppCompatImageButton + @Suppress("MagicNumber") + private val imageLoadedCounter: AtomicInteger = AtomicInteger(4) + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -68,6 +74,7 @@ internal class ImageComponentsFragment : Fragment() { object : ImageLoadedCallback { override fun onImageLoaded(resource: Drawable) { imageViewRemote.setImageDrawable(resource) + decrementLoadingCounter() } } ) @@ -80,6 +87,7 @@ internal class ImageComponentsFragment : Fragment() { object : ImageLoadedCallback { override fun onImageLoaded(resource: Drawable) { buttonRemote.setCompoundDrawablesWithIntrinsicBounds(null, null, null, resource) + decrementLoadingCounter() } } ) @@ -92,6 +100,7 @@ internal class ImageComponentsFragment : Fragment() { object : ImageLoadedCallback { override fun onImageLoaded(resource: Drawable) { textViewRemote.setCompoundDrawablesWithIntrinsicBounds(null, null, null, resource) + decrementLoadingCounter() } } ) @@ -105,11 +114,19 @@ internal class ImageComponentsFragment : Fragment() { override fun onImageLoaded(resource: Drawable) { imageButtonRemote.background = resource appCompatButtonRemote.background = resource + decrementLoadingCounter() } } ) } + @OptIn(ExperimentalRumApi::class) + private fun decrementLoadingCounter() { + if (imageLoadedCounter.decrementAndGet() == 0) { + GlobalRumMonitor.get().addViewLoadingTime() + } + } + // endregion companion object { diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt index fb0376cf65..d01a719262 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt @@ -6,7 +6,7 @@ package com.datadog.android.sample.sessionreplay -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment -internal class ImageScalingFragment : Fragment(R.layout.fragment_image_scaling) +internal class ImageScalingFragment : SynchronousLoadedFragment(R.layout.fragment_image_scaling) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt index 9b7df370a1..a3f5f26da2 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt @@ -12,13 +12,13 @@ import android.view.View import android.view.ViewGroup import android.widget.EditText import android.widget.ImageButton -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.timepicker.MaterialTimePicker import java.util.Locale -internal class PickerComponentsFragment : Fragment() { +internal class PickerComponentsFragment : SynchronousLoadedFragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt index 949f5ae1b1..0a6ef02f17 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt @@ -10,10 +10,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment -internal class PrivacySensitiveTextComponentsFragment : Fragment() { +internal class PrivacySensitiveTextComponentsFragment : SynchronousLoadedFragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt index 565bd80385..31a0d2e0b4 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt @@ -9,10 +9,10 @@ package com.datadog.android.sample.sessionreplay import android.os.Bundle import android.view.View import android.widget.CheckBox -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment -internal class RadioCheckBoxesFragment : Fragment(R.layout.fragment_radio_checkbox_components) { +internal class RadioCheckBoxesFragment : SynchronousLoadedFragment(R.layout.fragment_radio_checkbox_components) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt index 4f26167117..7e4002582c 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt @@ -12,13 +12,13 @@ import android.view.ViewGroup import android.widget.Button import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.children -import androidx.fragment.app.Fragment import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment.Companion.findNavController import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment internal class SessionReplayFragment : - Fragment(), + SynchronousLoadedFragment(), View.OnClickListener { lateinit var navController: NavController diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt index 2ebfd4148f..3830610fa9 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt @@ -11,10 +11,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.NumberPicker -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment -internal class SlidersSteppersFragment : Fragment() { +internal class SlidersSteppersFragment : SynchronousLoadedFragment() { @Suppress("MagicNumber") override fun onCreateView( diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt index 07ab4c4ff4..2a4f443748 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt @@ -12,10 +12,10 @@ import android.view.View import android.view.ViewGroup import android.widget.CheckedTextView import androidx.appcompat.widget.AppCompatCheckedTextView -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment -internal class TextViewComponentsFragment : Fragment() { +internal class TextViewComponentsFragment : SynchronousLoadedFragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt index 2fad938042..9d7a6592ab 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt @@ -9,11 +9,11 @@ package com.datadog.android.sample.sessionreplay import android.content.Context import android.util.AttributeSet import android.widget.Toolbar -import androidx.fragment.app.Fragment import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment import androidx.appcompat.widget.Toolbar as AppCompatToolbar -internal class UnsupportedViewsFragment : Fragment(R.layout.fragment_unsupported_views) +internal class UnsupportedViewsFragment : SynchronousLoadedFragment(R.layout.fragment_unsupported_views) internal class AppcompatToolbarCustomSubclass(context: Context, attrs: AttributeSet) : AppCompatToolbar(context, attrs) internal class ToolbarCustomSubclass(context: Context, attrs: AttributeSet) : Toolbar(context, attrs) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt index 4d98dd60e5..f346f38aa2 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt @@ -17,6 +17,7 @@ import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button import androidx.fragment.app.Fragment +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.R import com.datadog.android.webview.WebViewTracking @@ -29,11 +30,15 @@ internal class WebViewRecordFragment : Fragment() { "datadoghq.dev" ) + @Volatile + private var pageIsLoaded: Boolean = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) } + @OptIn(ExperimentalRumApi::class) @SuppressLint("SetJavaScriptEnabled") override fun onCreateView( inflater: LayoutInflater, @@ -47,12 +52,21 @@ internal class WebViewRecordFragment : Fragment() { ) startCustomRumViewButton = rootView.findViewById(R.id.start_custom_rum_view_button) webView = rootView.findViewById(R.id.webview) - webView.webViewClient = WebViewClient() + webView.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + if (!pageIsLoaded) { + GlobalRumMonitor.get().addViewLoadingTime() + pageIsLoaded = true + } + } + } webView.settings.javaScriptEnabled = true WebViewTracking.enable(webView, webViewTrackingHosts) startCustomRumViewButton.setOnClickListener { GlobalRumMonitor.get().startView(this, "Custom RUM View") } + // add webview on load listener return rootView } diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt index 638cf5af1e..c9aeccb937 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt @@ -12,12 +12,12 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.ProgressBar -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R import com.datadog.android.sample.SampleApplication +import com.datadog.android.sample.SynchronousLoadedFragment -internal class OtelTracesFragment : Fragment(), View.OnClickListener { +internal class OtelTracesFragment : SynchronousLoadedFragment(), View.OnClickListener { lateinit var viewModel: OtelTracesViewModel lateinit var progressBarAsync: ProgressBar diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt index 48997b76a1..12989c1be6 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt @@ -14,12 +14,12 @@ import android.widget.Button import android.widget.ImageView import android.widget.ProgressBar import androidx.annotation.DrawableRes -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R import com.datadog.android.sample.SampleApplication +import com.datadog.android.sample.SynchronousLoadedFragment -internal class TracesFragment : Fragment(), View.OnClickListener { +internal class TracesFragment : SynchronousLoadedFragment(), View.OnClickListener { lateinit var viewModel: TracesViewModel lateinit var progressBarAsync: ProgressBar diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt index 1287633350..5d99fbc7ac 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt @@ -11,14 +11,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText -import androidx.fragment.app.Fragment import com.datadog.android.Datadog import com.datadog.android.sample.Preferences import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment import com.datadog.android.trace.withinSpan import com.google.android.material.snackbar.Snackbar -internal class UserFragment : Fragment(), View.OnClickListener { +internal class UserFragment : SynchronousLoadedFragment(), View.OnClickListener { lateinit var idField: EditText lateinit var nameField: EditText diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt index f3b511ef9d..0a530876ea 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt @@ -15,6 +15,8 @@ import android.webkit.WebView import android.webkit.WebViewClient import android.widget.TextView import androidx.fragment.app.Fragment +import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.R import com.datadog.android.webview.WebViewTracking @@ -31,6 +33,10 @@ internal open class PagerChildFragment : Fragment() { ) private lateinit var webView: WebView + @Volatile + private var pageWasLoaded: Boolean = false + + @OptIn(ExperimentalRumApi::class) @SuppressLint("SetJavaScriptEnabled") override fun onCreateView( inflater: LayoutInflater, @@ -42,7 +48,15 @@ internal open class PagerChildFragment : Fragment() { webView = view.findViewById(R.id.webview) - webView.webViewClient = WebViewClient() + webView.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + if (!pageWasLoaded) { + GlobalRumMonitor.get().addViewLoadingTime() + pageWasLoaded = true + } + } + } webView.settings.javaScriptEnabled = true WebViewTracking.enable(webView, webViewTrackingHosts) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt index 8078e7e583..43fba8121a 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt @@ -12,12 +12,12 @@ import android.view.ViewGroup import android.widget.CheckBox import android.widget.CompoundButton import android.widget.ProgressBar -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R +import com.datadog.android.sample.SynchronousLoadedFragment internal class VitalsFragment : - Fragment(), + SynchronousLoadedFragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt index f275d9f41c..325ecb8048 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt @@ -14,6 +14,8 @@ import android.webkit.WebView import android.webkit.WebViewClient import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders +import com.datadog.android.rum.ExperimentalRumApi +import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.R import com.datadog.android.sample.SampleApplication import com.datadog.android.webview.WebViewTracking @@ -27,8 +29,12 @@ internal class WebFragment : Fragment() { "datadoghq.dev" ) + @Volatile + private var pageWasLoaded: Boolean = false + // region Fragment Lifecycle + @OptIn(ExperimentalRumApi::class) @SuppressLint("SetJavaScriptEnabled") override fun onCreateView( inflater: LayoutInflater, @@ -37,7 +43,15 @@ internal class WebFragment : Fragment() { ): View? { val rootView = inflater.inflate(R.layout.fragment_web, container, false) webView = rootView.findViewById(R.id.webview) - webView.webViewClient = WebViewClient() + webView.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + if (!pageWasLoaded) { + GlobalRumMonitor.get().addViewLoadingTime() + pageWasLoaded = true + } + } + } webView.settings.javaScriptEnabled = true WebViewTracking.enable(webView, webViewTrackingHosts) return rootView From 98357f2a0c6241f131707590351a73fc9aa66890 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Mon, 9 Sep 2024 14:13:45 +0300 Subject: [PATCH 023/111] Modify the addViewLoadingTime API according with new RFC --- features/dd-sdk-android-rum/api/apiSurface | 2 +- .../api/dd-sdk-android-rum.api | 2 +- .../com/datadog/android/rum/RumMonitor.kt | 10 ++- .../rum/internal/domain/scope/RumRawEvent.kt | 1 + .../rum/internal/domain/scope/RumViewScope.kt | 10 ++- .../rum/internal/monitor/DatadogRumMonitor.kt | 4 +- .../internal/domain/scope/RumViewScopeTest.kt | 88 +++++++++++++++++-- .../internal/monitor/DatadogRumMonitorTest.kt | 6 +- 8 files changed, 104 insertions(+), 19 deletions(-) diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index 074d735e04..e593e97725 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -112,7 +112,7 @@ interface com.datadog.android.rum.RumMonitor fun getAttributes(): Map fun clearAttributes() fun stopSession() - fun addViewLoadingTime() + fun addViewLoadingTime(Boolean) var debug: Boolean fun _getInternal(): _RumInternalProxy? enum com.datadog.android.rum.RumPerformanceMetric diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index c6b78820f7..9787512860 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -144,7 +144,7 @@ public abstract interface class com/datadog/android/rum/RumMonitor { public abstract fun addFeatureFlagEvaluation (Ljava/lang/String;Ljava/lang/Object;)V public abstract fun addFeatureFlagEvaluations (Ljava/util/Map;)V public abstract fun addTiming (Ljava/lang/String;)V - public abstract fun addViewLoadingTime ()V + public abstract fun addViewLoadingTime (Z)V public abstract fun clearAttributes ()V public abstract fun getAttributes ()Ljava/util/Map; public abstract fun getCurrentSessionId (Lkotlin/jvm/functions/Function1;)V diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt index cb96078774..7ff6ffdb86 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumMonitor.kt @@ -324,13 +324,15 @@ interface RumMonitor { fun stopSession() /** - * Adds view loading time to current RUM view based on the time elapsed since the view was started. + * Adds view loading time RUM active view based on the time elapsed since the view was started. + * The view loading time is automatically calculated as the difference between the current time + * and the start time of the view. * This method should be called only once per view. - * If the view is not started, this method does nothing. - * If the view is not active(stopped), this method does nothing. + * If no view is started or active, this method does nothing. + * @param overwrite which controls if the method overwrites the previously calculated view loading time. */ @ExperimentalRumApi - fun addViewLoadingTime() + fun addViewLoadingTime(overwrite: Boolean) /** * Utility setting to inspect the active RUM View. diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt index 7d61d3b45e..87c390623a 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt @@ -174,6 +174,7 @@ internal sealed class RumRawEvent { ) : RumRawEvent() internal data class AddViewLoadingTime( + val overwrite: Boolean, override val eventTime: Time = Time() ) : RumRawEvent() diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index c4bfc98112..19a9c22349 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -241,9 +241,13 @@ internal open class RumViewScope( @WorkerThread private fun onAddViewLoadingTime(event: RumRawEvent.AddViewLoadingTime, writer: DataWriter) { if (stopped) return - - viewLoadingTime = event.eventTime.nanoTime - startedNanos - sendViewUpdate(event, writer) + val canAddViewLoadingTime = event.overwrite || viewLoadingTime == null + if (canAddViewLoadingTime) { + viewLoadingTime = event.eventTime.nanoTime - startedNanos + sendViewUpdate(event, writer) + } else { + // TODO: RUM-6031 Add logs and telemetry here + } } @WorkerThread diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index 1abea4f039..a7b3bb77df 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -541,8 +541,8 @@ internal class DatadogRumMonitor( } @ExperimentalRumApi - override fun addViewLoadingTime() { - handleEvent(RumRawEvent.AddViewLoadingTime()) + override fun addViewLoadingTime(overwrite: Boolean) { + handleEvent(RumRawEvent.AddViewLoadingTime(overwrite = overwrite)) } override fun addLongTask(durationNs: Long, target: String) { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index aa5d6c379a..b0fb1f7964 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -6963,9 +6963,11 @@ internal class RumViewScopeTest { // region View Loading Time @Test - fun `M send event with view loading time W handleEvent(AddViewLoadingTime) on active view`() { + fun `M send event with view loading time W handleEvent(AddViewLoadingTime) on active view`( + @BoolForgery fakeOverwrite: Boolean + ) { // Given - val viewLoadingTimeEvent = RumRawEvent.AddViewLoadingTime() + val viewLoadingTimeEvent = RumRawEvent.AddViewLoadingTime(overwrite = fakeOverwrite) val expectedViewLoadingTime = viewLoadingTimeEvent.eventTime.nanoTime - fakeEventTime.nanoTime // When @@ -7030,11 +7032,11 @@ internal class RumViewScopeTest { } @Test - fun `M send event with view loading time W handleEvent(AddViewLoadingTime) on active view called multiple times`( + fun `M update view loading time each time W handleEvent(AddViewLoadingTime, overwrite=true) multi calls`( forge: Forge ) { // Given - val viewLoadingTimeEvents = forge.aList { RumRawEvent.AddViewLoadingTime() } + val viewLoadingTimeEvents = forge.aList { RumRawEvent.AddViewLoadingTime(overwrite = true) } val expectedViewLoadingTime = viewLoadingTimeEvents.last().eventTime.nanoTime - fakeEventTime.nanoTime // When @@ -7102,13 +7104,87 @@ internal class RumViewScopeTest { } @Test - fun `M not update the view loading time W handleEvent(AddViewLoadingTime) on stopped view`() { + fun `M update view loading time only first time W handleEvent(AddViewLoadingTime, overwrite=false) multi calls`( + forge: Forge + ) { + // Given + val viewLoadingTimeEvents = forge.aList { RumRawEvent.AddViewLoadingTime(overwrite = false) } + val expectedViewLoadingTime = viewLoadingTimeEvents.first().eventTime.nanoTime - fakeEventTime.nanoTime + + // When + viewLoadingTimeEvents.forEach { + testedScope.handleEvent( + it, + mockWriter + ) + } + + // Then + argumentCaptor { + verify(mockWriter) + .write(eq(mockEventBatchWriter), capture(), eq(EventType.DEFAULT)) + assertThat(lastValue) + .apply { + hasTimestamp(resolveExpectedTimestamp(fakeEventTime.timestamp)) + hasName(fakeKey.name) + hasUrl(fakeUrl) + hasDurationGreaterThan(1) + hasLoadingType(null) + hasVersion(2) + hasErrorCount(0) + hasResourceCount(0) + hasActionCount(0) + hasFrustrationCount(0) + hasLongTaskCount(0) + hasFrozenFrameCount(0) + hasCpuMetric(null) + hasMemoryMetric(null, null) + hasRefreshRateMetric(null, null) + isActive(true) + isSlowRendered(false) + hasUserInfo(fakeDatadogContext.userInfo) + hasViewId(testedScope.viewId) + hasApplicationId(fakeParentContext.applicationId) + hasSessionId(fakeParentContext.sessionId) + hasUserSession() + hasNoSyntheticsTest() + hasStartReason(fakeParentContext.sessionStartReason) + hasReplay(fakeHasReplay) + hasReplayStats(fakeReplayStats) + hasSource(fakeSourceViewEvent) + hasLoadingTime(expectedViewLoadingTime) + hasDeviceInfo( + fakeDatadogContext.deviceInfo.deviceName, + fakeDatadogContext.deviceInfo.deviceModel, + fakeDatadogContext.deviceInfo.deviceBrand, + fakeDatadogContext.deviceInfo.deviceType.toViewSchemaType(), + fakeDatadogContext.deviceInfo.architecture + ) + hasOsInfo( + fakeDatadogContext.deviceInfo.osName, + fakeDatadogContext.deviceInfo.osVersion, + fakeDatadogContext.deviceInfo.osMajorVersion + ) + hasConnectivityInfo(fakeDatadogContext.networkInfo) + hasServiceName(fakeDatadogContext.service) + hasVersion(fakeDatadogContext.version) + hasSessionActive(fakeParentContext.isSessionActive) + hasSampleRate(fakeSampleRate) + } + } + verifyNoMoreInteractions(mockWriter) + } + + @Test + fun `M not update the view loading time W handleEvent(AddViewLoadingTime) on stopped view`( + @BoolForgery fakeOverwrite: Boolean + ) { // Given testedScope.stopped = true // When testedScope.handleEvent( - RumRawEvent.AddViewLoadingTime(), + RumRawEvent.AddViewLoadingTime(overwrite = fakeOverwrite), mockWriter ) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 4c1fe84d02..36256f2ec3 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -877,8 +877,10 @@ internal class DatadogRumMonitorTest { @Test @OptIn(ExperimentalRumApi::class) - fun `M delegate event to rootScope W addViewLoadTime()`() { - testedMonitor.addViewLoadingTime() + fun `M delegate event to rootScope W addViewLoadTime()`( + @BoolForgery fakeOverride: Boolean + ) { + testedMonitor.addViewLoadingTime(fakeOverride) Thread.sleep(PROCESSING_DELAY) argumentCaptor { From 02d35655a1360eaa4dd9da0a02e6b0b601563497 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Mon, 9 Sep 2024 15:24:27 +0300 Subject: [PATCH 024/111] Modify the sample app based on latest changes on addViewLoadingTime API --- .../rum/internal/domain/scope/RumViewScope.kt | 2 +- .../internal/monitor/DatadogRumMonitorTest.kt | 4 +-- .../sample/SynchronousLoadedFragment.kt | 25 --------------- .../android/sample/ViewPagerFragment.kt | 2 +- .../android/sample/about/AboutFragment.kt | 4 +-- .../sample/compose/JetpackComposeActivity.kt | 5 +-- .../sample/compose/NavigationSample.kt | 32 ------------------- .../android/sample/crash/CrashFragment.kt | 4 +-- .../sample/datalist/DataListFragment.kt | 2 +- .../android/sample/gdpr/GdprDialogFragment.kt | 8 ----- .../android/sample/home/HomeFragment.kt | 4 +-- .../android/sample/logs/LogsFragment.kt | 4 +-- .../android/sample/picture/PictureFragment.kt | 4 +-- .../sessionreplay/DifferentFontsFragment.kt | 4 +-- .../DropDownSwitchersFragment.kt | 4 +-- .../sessionreplay/ImageComponentsFragment.kt | 7 ++-- .../sessionreplay/ImageScalingFragment.kt | 4 +-- .../sessionreplay/PickerComponentsFragment.kt | 4 +-- .../PrivacySensitiveTextComponentsFragment.kt | 4 +-- .../sessionreplay/RadioCheckBoxesFragment.kt | 4 +-- .../sessionreplay/SessionReplayFragment.kt | 4 +-- .../sessionreplay/SlidersSteppersFragment.kt | 4 +-- .../TextViewComponentsFragment.kt | 4 +-- .../sessionreplay/UnsupportedViewsFragment.kt | 4 +-- .../sessionreplay/WebViewRecordFragment.kt | 8 +---- .../sample/traces/OtelTracesFragment.kt | 4 +-- .../android/sample/traces/TracesFragment.kt | 4 +-- .../android/sample/user/UserFragment.kt | 4 +-- .../sample/viewpager/PagerChildFragment.kt | 8 +---- .../android/sample/vitals/VitalsFragment.kt | 4 +-- .../android/sample/webview/WebFragment.kt | 8 +---- 31 files changed, 50 insertions(+), 137 deletions(-) delete mode 100644 sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 19a9c22349..8834bbf3b2 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -246,7 +246,7 @@ internal open class RumViewScope( viewLoadingTime = event.eventTime.nanoTime - startedNanos sendViewUpdate(event, writer) } else { - // TODO: RUM-6031 Add logs and telemetry here + // TODO RUM-6031 Add logs and telemetry here } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index 36256f2ec3..e0e50371fe 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -878,9 +878,9 @@ internal class DatadogRumMonitorTest { @Test @OptIn(ExperimentalRumApi::class) fun `M delegate event to rootScope W addViewLoadTime()`( - @BoolForgery fakeOverride: Boolean + @BoolForgery fakeOverwrite: Boolean ) { - testedMonitor.addViewLoadingTime(fakeOverride) + testedMonitor.addViewLoadingTime(fakeOverwrite) Thread.sleep(PROCESSING_DELAY) argumentCaptor { diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt deleted file mode 100644 index 307fb01036..0000000000 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SynchronousLoadedFragment.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.sample - -import androidx.annotation.LayoutRes -import androidx.fragment.app.Fragment -import com.datadog.android.rum.ExperimentalRumApi -import com.datadog.android.rum.GlobalRumMonitor - -internal open class SynchronousLoadedFragment : Fragment { - - constructor() : super() - - constructor(@LayoutRes contentLayoutId: Int) : super(contentLayoutId) - - @OptIn(ExperimentalRumApi::class) - override fun onResume() { - super.onResume() - GlobalRumMonitor.get().addViewLoadingTime() - } -} diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt index 5ad121ef5e..b755c5c466 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/ViewPagerFragment.kt @@ -26,7 +26,7 @@ import com.google.android.material.tabs.TabLayoutMediator /** * An [Activity] with a [ViewPager] holding multiple fragments. */ -class ViewPagerFragment : SynchronousLoadedFragment() { +class ViewPagerFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt index 93afc0d04f..4a00488eef 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/about/AboutFragment.kt @@ -11,12 +11,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment internal class AboutFragment : - SynchronousLoadedFragment() { + Fragment() { private lateinit var viewModel: AboutViewModel private lateinit var aboutText: TextView diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt index 6e622a0f29..af275c0304 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/JetpackComposeActivity.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver -import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.google.accompanist.appcompattheme.AppCompatTheme import com.google.accompanist.pager.ExperimentalPagerApi @@ -39,7 +38,7 @@ import kotlinx.coroutines.launch class JetpackComposeActivity : AppCompatActivity() { @Suppress("LongMethod") - @OptIn(ExperimentalPagerApi::class, ExperimentalRumApi::class) + @OptIn(ExperimentalPagerApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { @@ -58,7 +57,6 @@ class JetpackComposeActivity : AppCompatActivity() { val screen = pages[pagerState.currentPage].trackingName if (event == Lifecycle.Event.ON_RESUME) { rumMonitor.startView(screen, screen) - rumMonitor.addViewLoadingTime() } else if (event == Lifecycle.Event.ON_PAUSE) { rumMonitor.stopView(screen) } @@ -78,7 +76,6 @@ class JetpackComposeActivity : AppCompatActivity() { .collect { page -> val screen = pages[page].trackingName GlobalRumMonitor.get().startView(screen, screen) - GlobalRumMonitor.get().addViewLoadingTime() } } diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt index 79aa3df060..7e80e8a97e 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/compose/NavigationSample.kt @@ -13,17 +13,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleOwner import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost @@ -33,8 +28,6 @@ import androidx.navigation.navArgument import com.datadog.android.compose.ExperimentalTrackingApi import com.datadog.android.compose.NavigationViewTrackingEffect import com.datadog.android.compose.trackClick -import com.datadog.android.rum.ExperimentalRumApi -import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.tracking.AcceptAllNavDestinations import java.lang.IllegalArgumentException import kotlin.random.Random @@ -42,12 +35,6 @@ import kotlin.random.Random @OptIn(ExperimentalTrackingApi::class) @Composable internal fun NavigationSampleView() { - val lifecycleOwner = LocalLifecycleOwner.current - DisposableEffect(lifecycleOwner) { - val observer = DatadogLifecycleObserver() - lifecycleOwner.lifecycle.addObserver(observer) - onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } - } val navController = rememberNavController().apply { NavigationViewTrackingEffect( navController = this, @@ -70,13 +57,6 @@ internal fun SimpleView( @PreviewParameter(provider = SimpleViewIdPreviewProvider::class) viewId: String, onNavigate: () -> Unit = {} ) { - val lifecycleOwner = LocalLifecycleOwner.current - DisposableEffect(lifecycleOwner) { - val observer = DatadogLifecycleObserver() - lifecycleOwner.lifecycle.addObserver(observer) - onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } - } - Column( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, @@ -149,15 +129,3 @@ internal sealed class Screen( internal fun oneOf(vararg items: T): T { return items[Random.nextInt(items.size)] } - -private class DatadogLifecycleObserver : LifecycleEventObserver { - private var viewIsLoaded = false - - @OptIn(ExperimentalRumApi::class) - override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { - if (event == Lifecycle.Event.ON_RESUME && !viewIsLoaded) { - GlobalRumMonitor.get().addViewLoadingTime() - viewIsLoaded = true - } - } -} diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt index a1d95f13ba..f95bba498f 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/crash/CrashFragment.kt @@ -15,13 +15,13 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import androidx.appcompat.widget.AppCompatSpinner +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment @Suppress("MagicNumber") internal class CrashFragment : - SynchronousLoadedFragment(), + Fragment(), View.OnClickListener { private lateinit var viewModel: CrashViewModel diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt index 29b5e8a948..e784b0d9ec 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/datalist/DataListFragment.kt @@ -65,7 +65,7 @@ internal class DataListFragment : Fragment() { is DataListViewModel.UIResponse.Success -> { if (!firstDataWasLoaded) { GlobalRumMonitor.get(Datadog.getInstance()).addTiming("logs_data_loaded") - GlobalRumMonitor.get().addViewLoadingTime() + GlobalRumMonitor.get().addViewLoadingTime(overwrite = false) firstDataWasLoaded = true } adapter.updateData(it.data) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt index b5230d58d0..688d38a33e 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/gdpr/GdprDialogFragment.kt @@ -15,8 +15,6 @@ import androidx.annotation.IdRes import androidx.fragment.app.DialogFragment import com.datadog.android.Datadog import com.datadog.android.privacy.TrackingConsent -import com.datadog.android.rum.ExperimentalRumApi -import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.Preferences import com.datadog.android.sample.R import com.datadog.android.sample.TrackingConsentChangeListener @@ -49,12 +47,6 @@ internal class GdprDialogFragment : DialogFragment() { return rootView } - @OptIn(ExperimentalRumApi::class) - override fun onResume() { - super.onResume() - GlobalRumMonitor.get().addViewLoadingTime() - } - @IdRes private fun resolveButtonIdFromConsent(trackingConsent: TrackingConsent): Int { return when (trackingConsent) { diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt index e9772d4450..bf4cb1fc84 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/home/HomeFragment.kt @@ -12,14 +12,14 @@ import android.view.ViewGroup import android.widget.Button import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.children +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment.Companion.findNavController import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment internal class HomeFragment : - SynchronousLoadedFragment(), + Fragment(), View.OnClickListener { private lateinit var viewModel: HomeViewModel diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt index 680b32af91..b3a9add74e 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/logs/LogsFragment.kt @@ -13,15 +13,15 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.EditText import android.widget.Spinner +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.log.Logger import com.datadog.android.sample.BuildConfig import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment @Suppress("DEPRECATION") internal class LogsFragment : - SynchronousLoadedFragment(), + Fragment(), View.OnClickListener { private var interactionsCount = 0 diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt index b207cbf347..69e94bcdbd 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/picture/PictureFragment.kt @@ -13,13 +13,13 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ImageView +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.datadog.android.sample.Preferences import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment internal class PictureFragment : - SynchronousLoadedFragment(), View.OnClickListener { + Fragment(), View.OnClickListener { private lateinit var picture: ImageView private lateinit var rootView: View diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt index 2360e8339f..c3c059857d 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DifferentFontsFragment.kt @@ -6,7 +6,7 @@ package com.datadog.android.sample.sessionreplay +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment -internal class DifferentFontsFragment : SynchronousLoadedFragment(R.layout.fragment_different_fonts) +internal class DifferentFontsFragment : Fragment(R.layout.fragment_different_fonts) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt index 001d9f8dc7..4e6614b42d 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/DropDownSwitchersFragment.kt @@ -13,10 +13,10 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.Spinner import androidx.appcompat.widget.SwitchCompat +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment -internal class DropDownSwitchersFragment : SynchronousLoadedFragment() { +internal class DropDownSwitchersFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val root = inflater.inflate(R.layout.fragment_drop_down_switchers_components, container, false) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt index b1040039ac..c774427b11 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageComponentsFragment.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.ViewModelProvider import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.sample.R -import java.util.concurrent.atomic.AtomicInteger internal interface ImageLoadedCallback { fun onImageLoaded(resource: Drawable) @@ -36,7 +35,7 @@ internal class ImageComponentsFragment : Fragment() { private lateinit var appCompatButtonRemote: AppCompatImageButton @Suppress("MagicNumber") - private val imageLoadedCounter: AtomicInteger = AtomicInteger(4) + private var imageLoadedCounter: Int = 4 override fun onCreateView( inflater: LayoutInflater, @@ -122,8 +121,8 @@ internal class ImageComponentsFragment : Fragment() { @OptIn(ExperimentalRumApi::class) private fun decrementLoadingCounter() { - if (imageLoadedCounter.decrementAndGet() == 0) { - GlobalRumMonitor.get().addViewLoadingTime() + if (--imageLoadedCounter == 0) { + GlobalRumMonitor.get().addViewLoadingTime(overwrite = false) } } diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt index d01a719262..fb0376cf65 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/ImageScalingFragment.kt @@ -6,7 +6,7 @@ package com.datadog.android.sample.sessionreplay +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment -internal class ImageScalingFragment : SynchronousLoadedFragment(R.layout.fragment_image_scaling) +internal class ImageScalingFragment : Fragment(R.layout.fragment_image_scaling) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt index a3f5f26da2..9b7df370a1 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PickerComponentsFragment.kt @@ -12,13 +12,13 @@ import android.view.View import android.view.ViewGroup import android.widget.EditText import android.widget.ImageButton +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.timepicker.MaterialTimePicker import java.util.Locale -internal class PickerComponentsFragment : SynchronousLoadedFragment() { +internal class PickerComponentsFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt index 0a6ef02f17..949f5ae1b1 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/PrivacySensitiveTextComponentsFragment.kt @@ -10,10 +10,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment -internal class PrivacySensitiveTextComponentsFragment : SynchronousLoadedFragment() { +internal class PrivacySensitiveTextComponentsFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt index 31a0d2e0b4..565bd80385 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/RadioCheckBoxesFragment.kt @@ -9,10 +9,10 @@ package com.datadog.android.sample.sessionreplay import android.os.Bundle import android.view.View import android.widget.CheckBox +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment -internal class RadioCheckBoxesFragment : SynchronousLoadedFragment(R.layout.fragment_radio_checkbox_components) { +internal class RadioCheckBoxesFragment : Fragment(R.layout.fragment_radio_checkbox_components) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt index 7e4002582c..4f26167117 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SessionReplayFragment.kt @@ -12,13 +12,13 @@ import android.view.ViewGroup import android.widget.Button import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.children +import androidx.fragment.app.Fragment import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment.Companion.findNavController import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment internal class SessionReplayFragment : - SynchronousLoadedFragment(), + Fragment(), View.OnClickListener { lateinit var navController: NavController diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt index 3830610fa9..2ebfd4148f 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/SlidersSteppersFragment.kt @@ -11,10 +11,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.NumberPicker +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment -internal class SlidersSteppersFragment : SynchronousLoadedFragment() { +internal class SlidersSteppersFragment : Fragment() { @Suppress("MagicNumber") override fun onCreateView( diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt index 2a4f443748..07ab4c4ff4 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/TextViewComponentsFragment.kt @@ -12,10 +12,10 @@ import android.view.View import android.view.ViewGroup import android.widget.CheckedTextView import androidx.appcompat.widget.AppCompatCheckedTextView +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment -internal class TextViewComponentsFragment : SynchronousLoadedFragment() { +internal class TextViewComponentsFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt index 9d7a6592ab..2fad938042 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/UnsupportedViewsFragment.kt @@ -9,11 +9,11 @@ package com.datadog.android.sample.sessionreplay import android.content.Context import android.util.AttributeSet import android.widget.Toolbar +import androidx.fragment.app.Fragment import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment import androidx.appcompat.widget.Toolbar as AppCompatToolbar -internal class UnsupportedViewsFragment : SynchronousLoadedFragment(R.layout.fragment_unsupported_views) +internal class UnsupportedViewsFragment : Fragment(R.layout.fragment_unsupported_views) internal class AppcompatToolbarCustomSubclass(context: Context, attrs: AttributeSet) : AppCompatToolbar(context, attrs) internal class ToolbarCustomSubclass(context: Context, attrs: AttributeSet) : Toolbar(context, attrs) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt index f346f38aa2..5c823b26ce 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/sessionreplay/WebViewRecordFragment.kt @@ -30,9 +30,6 @@ internal class WebViewRecordFragment : Fragment() { "datadoghq.dev" ) - @Volatile - private var pageIsLoaded: Boolean = false - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) @@ -55,10 +52,7 @@ internal class WebViewRecordFragment : Fragment() { webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) - if (!pageIsLoaded) { - GlobalRumMonitor.get().addViewLoadingTime() - pageIsLoaded = true - } + GlobalRumMonitor.get().addViewLoadingTime(overwrite = false) } } webView.settings.javaScriptEnabled = true diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt index c9aeccb937..638cf5af1e 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/OtelTracesFragment.kt @@ -12,12 +12,12 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.ProgressBar +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R import com.datadog.android.sample.SampleApplication -import com.datadog.android.sample.SynchronousLoadedFragment -internal class OtelTracesFragment : SynchronousLoadedFragment(), View.OnClickListener { +internal class OtelTracesFragment : Fragment(), View.OnClickListener { lateinit var viewModel: OtelTracesViewModel lateinit var progressBarAsync: ProgressBar diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt index 12989c1be6..48997b76a1 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt @@ -14,12 +14,12 @@ import android.widget.Button import android.widget.ImageView import android.widget.ProgressBar import androidx.annotation.DrawableRes +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R import com.datadog.android.sample.SampleApplication -import com.datadog.android.sample.SynchronousLoadedFragment -internal class TracesFragment : SynchronousLoadedFragment(), View.OnClickListener { +internal class TracesFragment : Fragment(), View.OnClickListener { lateinit var viewModel: TracesViewModel lateinit var progressBarAsync: ProgressBar diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt index 5d99fbc7ac..1287633350 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/user/UserFragment.kt @@ -11,14 +11,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText +import androidx.fragment.app.Fragment import com.datadog.android.Datadog import com.datadog.android.sample.Preferences import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment import com.datadog.android.trace.withinSpan import com.google.android.material.snackbar.Snackbar -internal class UserFragment : SynchronousLoadedFragment(), View.OnClickListener { +internal class UserFragment : Fragment(), View.OnClickListener { lateinit var idField: EditText lateinit var nameField: EditText diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt index 0a530876ea..490e81b5db 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/viewpager/PagerChildFragment.kt @@ -33,9 +33,6 @@ internal open class PagerChildFragment : Fragment() { ) private lateinit var webView: WebView - @Volatile - private var pageWasLoaded: Boolean = false - @OptIn(ExperimentalRumApi::class) @SuppressLint("SetJavaScriptEnabled") override fun onCreateView( @@ -51,10 +48,7 @@ internal open class PagerChildFragment : Fragment() { webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) - if (!pageWasLoaded) { - GlobalRumMonitor.get().addViewLoadingTime() - pageWasLoaded = true - } + GlobalRumMonitor.get().addViewLoadingTime(overwrite = false) } } webView.settings.javaScriptEnabled = true diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt index 43fba8121a..8078e7e583 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/vitals/VitalsFragment.kt @@ -12,12 +12,12 @@ import android.view.ViewGroup import android.widget.CheckBox import android.widget.CompoundButton import android.widget.ProgressBar +import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders import com.datadog.android.sample.R -import com.datadog.android.sample.SynchronousLoadedFragment internal class VitalsFragment : - SynchronousLoadedFragment(), + Fragment(), View.OnClickListener, CompoundButton.OnCheckedChangeListener { diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt index 325ecb8048..d1f0e1863a 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/webview/WebFragment.kt @@ -29,9 +29,6 @@ internal class WebFragment : Fragment() { "datadoghq.dev" ) - @Volatile - private var pageWasLoaded: Boolean = false - // region Fragment Lifecycle @OptIn(ExperimentalRumApi::class) @@ -46,10 +43,7 @@ internal class WebFragment : Fragment() { webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) - if (!pageWasLoaded) { - GlobalRumMonitor.get().addViewLoadingTime() - pageWasLoaded = true - } + GlobalRumMonitor.get().addViewLoadingTime(overwrite = false) } } webView.settings.javaScriptEnabled = true From 804969f16bc8dd2a0d42012fa9491cf67435dde3 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Wed, 11 Sep 2024 14:14:49 +0300 Subject: [PATCH 025/111] RUM-6129 Introduce the API usage telemetry event and API --- dd-sdk-android-core/api/apiSurface | 1 + .../api/dd-sdk-android-core.api | 1 + dd-sdk-android-core/build.gradle.kts | 1 + .../com/datadog/android/_InternalProxy.kt | 40 +- .../com/datadog/android/api/InternalLogger.kt | 13 + .../android/core/internal/DatadogCore.kt | 26 +- .../core/internal/logger/SdkInternalLogger.kt | 64 +- .../com/datadog/android/InternalProxyTest.kt | 48 +- .../datadog/android/core/DatadogCoreTest.kt | 49 ++ .../internal/logger/SdkInternalLoggerTest.kt | 199 +++-- .../android/utils/forge/Configurator.kt | 4 + dd-sdk-android-internal/api/apiSurface | 16 + .../api/dd-sdk-android-internal.api | 73 ++ dd-sdk-android-internal/build.gradle.kts | 16 + .../internal/telemetry/TelemetryEvent.kt | 48 ++ .../TelemetryApiUsageEventForgeryFactory.kt | 23 + .../elmyr/TelemetryEventForgeryFactory.kt | 28 + ...nternalConfigurationEventForgeryFactory.kt | 25 + .../TelemetryLogDebugEventForgeryFactory.kt | 21 + .../TelemetryLogErrorEventForgeryFactory.kt | 25 + .../TelemetryMetricEventForgeryFactory.kt | 21 + features/dd-sdk-android-rum/api/apiSurface | 229 ++++++ .../api/dd-sdk-android-rum.api | 704 ++++++++++++++++++ features/dd-sdk-android-rum/build.gradle.kts | 1 + .../generate_telemetry_models.gradle.kts | 4 - .../src/main/json/telemetry/usage-schema.json | 3 + .../usage/common-features-schema.json | 8 + .../usage/mobile-features-schema.json | 32 + .../android/rum/internal/RumFeature.kt | 98 +-- .../rum/internal/domain/scope/RumRawEvent.kt | 16 +- .../internal/monitor/AdvancedRumMonitor.kt | 28 +- .../rum/internal/monitor/DatadogRumMonitor.kt | 100 +-- .../android/telemetry/internal/Telemetry.kt | 51 -- .../internal/TelemetryCoreConfiguration.kt | 59 -- .../telemetry/internal/TelemetryEventExt.kt | 18 + .../internal/TelemetryEventHandler.kt | 129 +++- .../telemetry/internal/TelemetryEventId.kt | 21 +- .../telemetry/internal/TelemetryType.kt | 4 +- .../android/rum/internal/RumFeatureTest.kt | 286 +------ .../rum/internal/metric/FakeInternalLogger.kt | 5 + .../internal/monitor/DatadogRumMonitorTest.kt | 172 +---- .../android/rum/utils/forge/Configurator.kt | 17 +- ...elemetryCoreConfigurationForgeryFactory.kt | 25 - .../TelemetryCoreConfigurationTest.kt | 74 -- .../internal/TelemetryEventHandlerTest.kt | 480 ++++++------ .../telemetry/internal/TelemetryTest.kt | 104 --- reliability/stub-core/build.gradle.kts | 1 + .../android/core/stub/StubInternalLogger.kt | 9 + 48 files changed, 2033 insertions(+), 1387 deletions(-) create mode 100644 dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/TelemetryEvent.kt create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryApiUsageEventForgeryFactory.kt create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryInternalConfigurationEventForgeryFactory.kt create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogDebugEventForgeryFactory.kt create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogErrorEventForgeryFactory.kt create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryMetricEventForgeryFactory.kt create mode 100644 features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json delete mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/Telemetry.kt delete mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfiguration.kt delete mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryCoreConfigurationForgeryFactory.kt delete mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfigurationTest.kt delete mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryTest.kt diff --git a/dd-sdk-android-core/api/apiSurface b/dd-sdk-android-core/api/apiSurface index fa1c9f1256..673a0e9ee6 100644 --- a/dd-sdk-android-core/api/apiSurface +++ b/dd-sdk-android-core/api/apiSurface @@ -44,6 +44,7 @@ interface com.datadog.android.api.InternalLogger fun log(Level, List, () -> String, Throwable? = null, Boolean = false, Map? = null) fun logMetric(() -> String, Map, Float) fun startPerformanceMeasure(String, com.datadog.android.core.metrics.TelemetryMetricType, Float, String): com.datadog.android.core.metrics.PerformanceMetric? + fun logApiUsage(com.datadog.android.internal.telemetry.TelemetryEvent.ApiUsage, Float) companion object val UNBOUND: InternalLogger interface com.datadog.android.api.SdkCore diff --git a/dd-sdk-android-core/api/dd-sdk-android-core.api b/dd-sdk-android-core/api/dd-sdk-android-core.api index c4959aa531..f32c80ce3e 100644 --- a/dd-sdk-android-core/api/dd-sdk-android-core.api +++ b/dd-sdk-android-core/api/dd-sdk-android-core.api @@ -77,6 +77,7 @@ public abstract interface class com/datadog/android/api/InternalLogger { public static final field Companion Lcom/datadog/android/api/InternalLogger$Companion; public abstract fun log (Lcom/datadog/android/api/InternalLogger$Level;Lcom/datadog/android/api/InternalLogger$Target;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;)V public abstract fun log (Lcom/datadog/android/api/InternalLogger$Level;Ljava/util/List;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;)V + public abstract fun logApiUsage (Lcom/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage;F)V public abstract fun logMetric (Lkotlin/jvm/functions/Function0;Ljava/util/Map;F)V public abstract fun startPerformanceMeasure (Ljava/lang/String;Lcom/datadog/android/core/metrics/TelemetryMetricType;FLjava/lang/String;)Lcom/datadog/android/core/metrics/PerformanceMetric; } diff --git a/dd-sdk-android-core/build.gradle.kts b/dd-sdk-android-core/build.gradle.kts index 4f57fb00e0..5fb7840a38 100644 --- a/dd-sdk-android-core/build.gradle.kts +++ b/dd-sdk-android-core/build.gradle.kts @@ -120,6 +120,7 @@ dependencies { ) } } + testImplementation(testFixtures(project(":dd-sdk-android-internal"))) testImplementation(libs.bundles.jUnit5) testImplementation(libs.bundles.testTools) unmock(libs.robolectric) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt index b2d6c0ce1a..d64c015f1a 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt @@ -12,6 +12,7 @@ import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.internal.DatadogCore +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.lint.InternalApi /** @@ -45,29 +46,40 @@ class _InternalProxy internal constructor( } fun debug(message: String) { - rumFeature?.sendEvent(mapOf("type" to "telemetry_debug", "message" to message)) + val telemetryEvent = TelemetryEvent.Log.Debug( + message = message, + additionalProperties = null + ) + rumFeature?.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) } fun error(message: String, throwable: Throwable? = null) { - rumFeature?.sendEvent( - mapOf( - "type" to "telemetry_error", - "message" to message, - "throwable" to throwable - ) + val telemetryEvent = TelemetryEvent.Log.Error( + message = message, + error = throwable ) + rumFeature?.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) } fun error(message: String, stack: String?, kind: String?) { - rumFeature?.sendEvent( - mapOf( - "type" to "telemetry_error", - "message" to message, - "stacktrace" to stack, - "kind" to kind - ) + val telemetryEvent = TelemetryEvent.Log.Error( + message = message, + stacktrace = stack, + kind = kind + ) + rumFeature?.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) + } + + // region Internal + + private fun bundleEventIntoTelemetry(event: Any): Map { + return mapOf( + "type" to "telemetry_event", + "event" to event ) } + + // endregion } @Suppress("PropertyName") diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt index 3047a13ffe..e31e21ccae 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt @@ -9,6 +9,7 @@ package com.datadog.android.api import com.datadog.android.core.internal.logger.SdkInternalLogger import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.lint.InternalApi import com.datadog.tools.annotation.NoOpImplementation @@ -132,6 +133,18 @@ interface InternalLogger { operationName: String ): PerformanceMetric? + /** + * Logs an API usage from the internal implementation. + * @param apiUsageEvent the API event being tracked + * @param samplingRate value between 0-100 for sampling the event. Note that the sampling rate applied to this + * event will be applied in addition to the global telemetry sampling rate. + */ + @InternalApi + fun logApiUsage( + apiUsageEvent: TelemetryEvent.ApiUsage, + samplingRate: Float + ) + companion object { /** diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt index 7f2d53b592..87a6aa2df7 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt @@ -38,6 +38,7 @@ import com.datadog.android.core.internal.utils.scheduleSafe import com.datadog.android.core.internal.utils.submitSafe import com.datadog.android.core.thread.FlushableExecutorService import com.datadog.android.error.internal.CrashReportsFeature +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.ndk.internal.NdkCrashHandler import com.datadog.android.privacy.TrackingConsent import com.google.gson.JsonObject @@ -499,20 +500,23 @@ internal class DatadogCore( } @Suppress("FunctionMaxLength") - private fun sendCoreConfigurationTelemetryEvent(configuration: Configuration) { + internal fun sendCoreConfigurationTelemetryEvent(configuration: Configuration) { val runnable = Runnable { val rumFeature = getFeature(Feature.RUM_FEATURE_NAME) ?: return@Runnable - val coreConfigurationEvent = mapOf( - "type" to "telemetry_configuration", - "track_errors" to (configuration.crashReportsEnabled), - "batch_size" to configuration.coreConfig.batchSize.windowDurationMs, - "batch_upload_frequency" to configuration.coreConfig.uploadFrequency.baseStepMs, - "use_proxy" to (configuration.coreConfig.proxy != null), - "use_local_encryption" to (configuration.coreConfig.encryption != null), - "batch_processing_level" to configuration.coreConfig.batchProcessingLevel.maxBatchesPerUploadJob, - "use_persistence_strategy_factory" to (configuration.coreConfig.persistenceStrategyFactory != null) + val event = TelemetryEvent.Configuration( + trackErrors = configuration.crashReportsEnabled, + batchSize = configuration.coreConfig.batchSize.windowDurationMs, + useProxy = configuration.coreConfig.proxy != null, + useLocalEncryption = configuration.coreConfig.encryption != null, + batchUploadFrequency = configuration.coreConfig.uploadFrequency.baseStepMs, + batchProcessingLevel = configuration.coreConfig.batchProcessingLevel.maxBatchesPerUploadJob + ) + rumFeature.sendEvent( + mapOf( + "type" to "telemetry_event", + "event" to event + ) ) - rumFeature.sendEvent(coreConfigurationEvent) } coreFeature.uploadExecutorService.scheduleSafe( diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt index 9b643efed7..1b13669b9c 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt @@ -16,6 +16,7 @@ import com.datadog.android.core.internal.metrics.MethodCalledTelemetry import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType import com.datadog.android.core.sampling.RateBasedSampler +import com.datadog.android.internal.telemetry.TelemetryEvent internal class SdkInternalLogger( private val sdkCore: FeatureSdkCore?, @@ -98,16 +99,12 @@ internal class SdkInternalLogger( samplingRate: Float ) { if (!RateBasedSampler(samplingRate).sample()) return - val rumFeature = sdkCore?.getFeature(Feature.RUM_FEATURE_NAME) ?: return - val message = messageBuilder() - val telemetryEvent = - mapOf( - TYPE_KEY to "mobile_metric", - MESSAGE_KEY to message, - ADDITIONAL_PROPERTIES_KEY to additionalProperties - ) - rumFeature.sendEvent(telemetryEvent) + val metricEvent = TelemetryEvent.Metric( + message = messageBuilder(), + additionalProperties = additionalProperties + ) + rumFeature.sendEvent(bundleEventIntoTelemetry(metricEvent)) } override fun startPerformanceMeasure( @@ -129,10 +126,26 @@ internal class SdkInternalLogger( } } + override fun logApiUsage( + apiUsageEvent: TelemetryEvent.ApiUsage, + samplingRate: Float + ) { + if (!RateBasedSampler(samplingRate).sample()) return + val rumFeature = sdkCore?.getFeature(Feature.RUM_FEATURE_NAME) ?: return + rumFeature.sendEvent(bundleEventIntoTelemetry(apiUsageEvent)) + } + // endregion // region Internal + private fun bundleEventIntoTelemetry(event: Any): Map { + return mapOf( + TYPE_KEY to TELEMETRY_EVENT_MESSAGE_TYPE, + TELEMETRY_EVENT_KEY to event + ) + } + private fun logToUser( level: InternalLogger.Level, messageBuilder: () -> String, @@ -210,26 +223,18 @@ internal class SdkInternalLogger( level == InternalLogger.Level.WARN || error != null ) { - mutableMapOf( - TYPE_KEY to "telemetry_error", - MESSAGE_KEY to message, - THROWABLE_KEY to error - ).apply { - if (!additionalProperties.isNullOrEmpty()) { - put(ADDITIONAL_PROPERTIES_KEY, additionalProperties) - } - } + TelemetryEvent.Log.Error( + message = message, + additionalProperties = additionalProperties, + error = error + ) } else { - mutableMapOf( - TYPE_KEY to "telemetry_debug", - MESSAGE_KEY to message - ).apply { - if (!additionalProperties.isNullOrEmpty()) { - put(ADDITIONAL_PROPERTIES_KEY, additionalProperties) - } - } + TelemetryEvent.Log.Debug( + message = message, + additionalProperties = additionalProperties + ) } - rumFeature.sendEvent(telemetryEvent) + rumFeature.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) } private fun InternalLogger.Level.toLogLevel(): Int { @@ -254,10 +259,9 @@ internal class SdkInternalLogger( companion object { internal const val SDK_LOG_TAG = "DD_LOG" internal const val DEV_LOG_TAG = "Datadog" - private const val MESSAGE_KEY = "message" private const val TYPE_KEY = "type" - private const val THROWABLE_KEY = "throwable" - private const val ADDITIONAL_PROPERTIES_KEY = "additionalProperties" + private const val TELEMETRY_EVENT_MESSAGE_TYPE = "telemetry_event" + internal const val TELEMETRY_EVENT_KEY = "event" } // endregion diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt index 4fa719f804..ec26ee4751 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt @@ -11,16 +11,19 @@ import com.datadog.android.api.feature.FeatureScope import com.datadog.android.core.internal.CoreFeature import com.datadog.android.core.internal.DatadogCore import com.datadog.android.core.internal.system.AppVersionProvider +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -49,12 +52,13 @@ internal class InternalProxyTest { proxy._telemetry.debug(message) // Then - verify(mockRumFeatureScope).sendEvent( - mapOf( - "type" to "telemetry_debug", - "message" to message - ) - ) + argumentCaptor> { + verify(mockRumFeatureScope).sendEvent(capture()) + val payload = firstValue + assert(payload["type"] == "telemetry_event") + val logEvent = payload["event"] as TelemetryEvent.Log.Debug + assertThat(logEvent.message).isEqualTo(message) + } } @Test @@ -73,14 +77,15 @@ internal class InternalProxyTest { proxy._telemetry.error(message, stack, kind) // Then - verify(mockRumFeatureScope).sendEvent( - mapOf( - "type" to "telemetry_error", - "message" to message, - "stacktrace" to stack, - "kind" to kind - ) - ) + argumentCaptor> { + verify(mockRumFeatureScope).sendEvent(capture()) + val payload = firstValue + assert(payload["type"] == "telemetry_event") + val logEvent = payload["event"] as TelemetryEvent.Log.Error + assertThat(logEvent.message).isEqualTo(message) + assertThat(logEvent.stacktrace).isEqualTo(stack) + assertThat(logEvent.kind).isEqualTo(kind) + } } @Test @@ -98,13 +103,14 @@ internal class InternalProxyTest { proxy._telemetry.error(message, throwable) // Then - verify(mockRumFeatureScope).sendEvent( - mapOf( - "type" to "telemetry_error", - "message" to message, - "throwable" to throwable - ) - ) + argumentCaptor> { + verify(mockRumFeatureScope).sendEvent(capture()) + val payload = firstValue + assert(payload["type"] == "telemetry_event") + val logEvent = payload["event"] as TelemetryEvent.Log.Error + assertThat(logEvent.message).isEqualTo(message) + assertThat(logEvent.error).isEqualTo(throwable) + } } @Test diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt index 010a48bd58..8cb119a3c7 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt @@ -29,6 +29,7 @@ import com.datadog.android.core.internal.time.NoOpTimeProvider import com.datadog.android.core.internal.time.TimeProvider import com.datadog.android.core.internal.user.MutableUserInfoProvider import com.datadog.android.core.thread.FlushableExecutorService +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.ndk.internal.NdkCrashHandler import com.datadog.android.privacy.TrackingConsent import com.datadog.android.utils.config.ApplicationContextTestConfiguration @@ -79,6 +80,7 @@ import java.util.Collections import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.Future +import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference @@ -157,6 +159,53 @@ internal class DatadogCoreTest { verify(mockConsentProvider).setConsent(fakeConsent) } + @Test + fun `M send configuration telemetry W initialize()`() { + // Given + val mockRumFeature = mock() + testedCore = DatadogCore( + appContext.mockInstance, + fakeInstanceId, + fakeInstanceName, + internalLoggerProvider = { mockInternalLogger }, + executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService }, + buildSdkVersionProvider = mockBuildSdkVersionProvider + ) + val mockUploadExecutorService = mock { executor -> + whenever(executor.schedule(any(), any(), any())) doAnswer { invocation -> + val runnable = invocation.getArgument(0) + runnable.run() + mock() + } + } + testedCore.features[Feature.RUM_FEATURE_NAME] = mockRumFeature + testedCore.initialize(fakeConfiguration) + + // When + testedCore.coreFeature.uploadExecutorService = mockUploadExecutorService + testedCore.sendCoreConfigurationTelemetryEvent(fakeConfiguration) + + // Then + argumentCaptor> { + verify(mockRumFeature).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val telemetryConfigurationEvent = firstValue["event"] as TelemetryEvent.Configuration + assertThat(telemetryConfigurationEvent.trackErrors) + .isEqualTo(fakeConfiguration.crashReportsEnabled) + assertThat(telemetryConfigurationEvent.batchSize) + .isEqualTo(fakeConfiguration.coreConfig.batchSize.windowDurationMs) + assertThat(telemetryConfigurationEvent.useLocalEncryption) + .isEqualTo(fakeConfiguration.coreConfig.encryption != null) + assertThat(telemetryConfigurationEvent.batchUploadFrequency) + .isEqualTo(fakeConfiguration.coreConfig.uploadFrequency.baseStepMs) + assertThat(telemetryConfigurationEvent.batchProcessingLevel) + .isEqualTo(fakeConfiguration.coreConfig.batchProcessingLevel.maxBatchesPerUploadJob) + assertThat(telemetryConfigurationEvent.useProxy) + .isEqualTo(fakeConfiguration.coreConfig.proxy != null) + } + } + @Test fun `M register feature W registerFeature()`( @Mock mockFeature: Feature, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt index 1765da8dbb..9cb82e55c1 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt @@ -14,11 +14,13 @@ import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.internal.metrics.MethodCalledTelemetry import com.datadog.android.core.metrics.TelemetryMetricType +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.utils.forge.Configurator import com.datadog.tools.unit.forge.aThrowable import com.datadog.tools.unit.forge.exhaustiveAttributes import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.FloatForgery +import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.annotation.StringForgeryType @@ -34,6 +36,7 @@ import org.junit.jupiter.api.extension.Extensions import org.mockito.Mock import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.mockingDetails @@ -280,13 +283,14 @@ internal class SdkInternalLoggerTest { ) // Then - verify(mockRumFeatureScope) - .sendEvent( - mapOf( - "type" to "telemetry_debug", - "message" to fakeMessage - ) - ) + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + assertThat(logEvent.message).isEqualTo(fakeMessage) + assertThat(logEvent.additionalProperties).isNull() + } } @Test @@ -314,14 +318,14 @@ internal class SdkInternalLoggerTest { ) // Then - verify(mockRumFeatureScope) - .sendEvent( - mapOf( - "type" to "telemetry_debug", - "message" to fakeMessage, - "additionalProperties" to fakeAdditionalProperties - ) - ) + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + assertThat(logEvent.message).isEqualTo(fakeMessage) + assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) + } } @Test @@ -346,13 +350,14 @@ internal class SdkInternalLoggerTest { ) // Then - verify(mockRumFeatureScope) - .sendEvent( - mapOf( - "type" to "telemetry_debug", - "message" to fakeMessage - ) - ) + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + assertThat(logEvent.message).isEqualTo(fakeMessage) + assertThat(logEvent.additionalProperties).isEmpty() + } } @Test @@ -378,15 +383,14 @@ internal class SdkInternalLoggerTest { ) // Then - verify(mockRumFeatureScope) - .sendEvent( - mapOf( - "type" to "telemetry_error", - "message" to fakeMessage, - "throwable" to null, - "additionalProperties" to fakeAdditionalProperties - ) - ) + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val logEvent = firstValue["event"] as TelemetryEvent.Log.Error + assertThat(logEvent.message).isEqualTo(fakeMessage) + assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) + } } @Test @@ -413,15 +417,15 @@ internal class SdkInternalLoggerTest { ) // Then - verify(mockRumFeatureScope) - .sendEvent( - mapOf( - "type" to "telemetry_error", - "message" to fakeMessage, - "throwable" to fakeThrowable, - "additionalProperties" to fakeAdditionalProperties - ) - ) + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val logEvent = firstValue["event"] as TelemetryEvent.Log.Error + assertThat(logEvent.message).isEqualTo(fakeMessage) + assertThat(logEvent.error).isEqualTo(fakeThrowable) + assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) + } } @Test @@ -448,13 +452,13 @@ internal class SdkInternalLoggerTest { } // Then - verify(mockRumFeatureScope) - .sendEvent( - mapOf( - "type" to "telemetry_debug", - "message" to fakeMessage - ) - ) + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + assertThat(logEvent.message).isEqualTo(fakeMessage) + } } @Test @@ -477,14 +481,14 @@ internal class SdkInternalLoggerTest { ) // Then - verify(mockRumFeatureScope) - .sendEvent( - mapOf( - "type" to "mobile_metric", - "message" to fakeMessage, - "additionalProperties" to fakeAdditionalProperties - ) - ) + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val metricEvent = firstValue["event"] as TelemetryEvent.Metric + assertThat(metricEvent.message).isEqualTo(fakeMessage) + assertThat(metricEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) + } } @Test @@ -518,7 +522,7 @@ internal class SdkInternalLoggerTest { } @Test - fun `M send metric W metric() {sampling 0 percent}`( + fun `M not send metric W metric() {sampling 0 percent}`( @StringForgery fakeMessage: String, forge: Forge ) { @@ -541,7 +545,7 @@ internal class SdkInternalLoggerTest { } @Test - fun `M do nothing metric W metric { rum feature not initialized }`( + fun `M do nothing W metric { rum feature not initialized }`( @StringForgery fakeMessage: String, @FloatForgery(0f, 100f) fakeSampleRate: Float, forge: Forge @@ -562,6 +566,87 @@ internal class SdkInternalLoggerTest { } } + @Test + fun `M send api usage telemetry W logApiUsage() { sampling rate 100 percent }`( + @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + ) { + // Given + val mockRumFeatureScope = mock() + whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope + + // When + testedInternalLogger.logApiUsage(fakeApiUsageTelemetryEvent, 100.0f) + + // Then + argumentCaptor>() { + verify(mockRumFeatureScope).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val apiUsageEvent = firstValue["event"] as TelemetryEvent.ApiUsage + assertThat(apiUsageEvent).isEqualTo(fakeApiUsageTelemetryEvent) + } + } + + @Test + fun `M send api usage telemetry W metric() {sampling x percent}`( + @FloatForgery(25f, 75f) fakeSampleRate: Float, + @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + ) { + // Given + val mockRumFeatureScope = mock() + whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope + val repeatCount = 100 + val expectedCallCount = (repeatCount * fakeSampleRate / 100f).toInt() + val marginOfError = (repeatCount * 0.25f).toInt() + + // When + repeat(100) { + testedInternalLogger.logApiUsage( + fakeApiUsageTelemetryEvent, + fakeSampleRate + ) + } + + // Then + val count = mockingDetails(mockRumFeatureScope).invocations.filter { it.method.name == "sendEvent" }.size + assertThat(count).isCloseTo(expectedCallCount, offset(marginOfError)) + } + + @Test + fun `M not send any api usage telemetry W logApiUsage() {sampling 0 percent}`( + @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + ) { + // Given + val mockRumFeatureScope = mock() + whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope + + // When + testedInternalLogger.logApiUsage( + fakeApiUsageTelemetryEvent, + 0.0f + ) + + // Then + verify(mockRumFeatureScope, never()).sendEvent(any()) + } + + @Test + fun `M do nothing W logApiUsage { rum feature not initialized }`( + @FloatForgery(0f, 100f) fakeSampleRate: Float, + @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + ) { + // Given + whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn null + + // When + assertDoesNotThrow { + testedInternalLogger.logApiUsage( + fakeApiUsageTelemetryEvent, + fakeSampleRate + ) + } + } + @Test fun `M create PerformanceMetric W startPerformanceMeasure() {MethodCalled, 100 percent}`( @StringForgery fakeCaller: String, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt index eea665baff..cf55cb6c31 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt @@ -6,6 +6,7 @@ package com.datadog.android.utils.forge +import com.datadog.android.internal.tests.elmyr.TelemetryApiUsageEventForgeryFactory import com.datadog.android.test.elmyr.PersistenceStrategyBatchForgeryFactory import com.datadog.android.tests.elmyr.useCoreFactories import com.datadog.tools.unit.forge.BaseConfigurator @@ -70,5 +71,8 @@ internal class Configurator : forge.addFactory(PersistenceStrategyBatchForgeryFactory()) forge.useJvmFactories() + + // telemetry + forge.addFactory(TelemetryApiUsageEventForgeryFactory()) } } diff --git a/dd-sdk-android-internal/api/apiSurface b/dd-sdk-android-internal/api/apiSurface index dac3f3d126..683725fde1 100644 --- a/dd-sdk-android-internal/api/apiSurface +++ b/dd-sdk-android-internal/api/apiSurface @@ -10,5 +10,21 @@ interface com.datadog.android.internal.profiler.BenchmarkTracer object com.datadog.android.internal.profiler.GlobalBenchmark fun register(BenchmarkProfiler) fun get(): BenchmarkProfiler +sealed class com.datadog.android.internal.telemetry.TelemetryEvent + sealed class Log : TelemetryEvent + constructor(String, Map?) + class Debug : Log + constructor(String, Map?) + class Error : Log + constructor(String, Map? = null, Throwable? = null, String? = null, String? = null) + data class Configuration : TelemetryEvent + constructor(Boolean, Long, Long, Boolean, Boolean, Int) + data class Metric : TelemetryEvent + constructor(String, Map?) + sealed class ApiUsage : TelemetryEvent + constructor(MutableMap = mutableMapOf()) + class AddViewLoadingTime : ApiUsage + constructor(Boolean, Boolean, Boolean, MutableMap = mutableMapOf()) + object InterceptorInstantiated : TelemetryEvent annotation com.datadog.tools.annotation.NoOpImplementation constructor(Boolean = false) diff --git a/dd-sdk-android-internal/api/dd-sdk-android-internal.api b/dd-sdk-android-internal/api/dd-sdk-android-internal.api index 0d686f1758..8a59b6bdcc 100644 --- a/dd-sdk-android-internal/api/dd-sdk-android-internal.api +++ b/dd-sdk-android-internal/api/dd-sdk-android-internal.api @@ -29,6 +29,79 @@ public final class com/datadog/android/internal/profiler/GlobalBenchmark { public final fun register (Lcom/datadog/android/internal/profiler/BenchmarkProfiler;)V } +public abstract class com/datadog/android/internal/telemetry/TelemetryEvent { +} + +public abstract class com/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage : com/datadog/android/internal/telemetry/TelemetryEvent { + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAdditionalProperties ()Ljava/util/Map; +} + +public final class com/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage$AddViewLoadingTime : com/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage { + public fun (ZZZLjava/util/Map;)V + public synthetic fun (ZZZLjava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getNoActiveView ()Z + public final fun getNoView ()Z + public final fun getOverwrite ()Z +} + +public final class com/datadog/android/internal/telemetry/TelemetryEvent$Configuration : com/datadog/android/internal/telemetry/TelemetryEvent { + public fun (ZJJZZI)V + public final fun component1 ()Z + public final fun component2 ()J + public final fun component3 ()J + public final fun component4 ()Z + public final fun component5 ()Z + public final fun component6 ()I + public final fun copy (ZJJZZI)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/internal/telemetry/TelemetryEvent$Configuration;ZJJZZIILjava/lang/Object;)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Configuration; + public fun equals (Ljava/lang/Object;)Z + public final fun getBatchProcessingLevel ()I + public final fun getBatchSize ()J + public final fun getBatchUploadFrequency ()J + public final fun getTrackErrors ()Z + public final fun getUseLocalEncryption ()Z + public final fun getUseProxy ()Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/internal/telemetry/TelemetryEvent$InterceptorInstantiated : com/datadog/android/internal/telemetry/TelemetryEvent { + public static final field INSTANCE Lcom/datadog/android/internal/telemetry/TelemetryEvent$InterceptorInstantiated; +} + +public abstract class com/datadog/android/internal/telemetry/TelemetryEvent$Log : com/datadog/android/internal/telemetry/TelemetryEvent { + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getMessage ()Ljava/lang/String; +} + +public final class com/datadog/android/internal/telemetry/TelemetryEvent$Log$Debug : com/datadog/android/internal/telemetry/TelemetryEvent$Log { + public fun (Ljava/lang/String;Ljava/util/Map;)V +} + +public final class com/datadog/android/internal/telemetry/TelemetryEvent$Log$Error : com/datadog/android/internal/telemetry/TelemetryEvent$Log { + public fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/Throwable;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/Throwable;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getError ()Ljava/lang/Throwable; + public final fun getKind ()Ljava/lang/String; + public final fun getStacktrace ()Ljava/lang/String; +} + +public final class com/datadog/android/internal/telemetry/TelemetryEvent$Metric : com/datadog/android/internal/telemetry/TelemetryEvent { + public fun (Ljava/lang/String;Ljava/util/Map;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Metric; + public static synthetic fun copy$default (Lcom/datadog/android/internal/telemetry/TelemetryEvent$Metric;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Metric; + public fun equals (Ljava/lang/Object;)Z + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getMessage ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public abstract interface annotation class com/datadog/tools/annotation/NoOpImplementation : java/lang/annotation/Annotation { public abstract fun publicNoOpImplementation ()Z } diff --git a/dd-sdk-android-internal/build.gradle.kts b/dd-sdk-android-internal/build.gradle.kts index d8e22c12c9..ff3ea51422 100644 --- a/dd-sdk-android-internal/build.gradle.kts +++ b/dd-sdk-android-internal/build.gradle.kts @@ -35,6 +35,10 @@ android { compileOptions { java17() } + + testFixtures { + enable = true + } } dependencies { @@ -42,6 +46,18 @@ dependencies { // Generate NoOp implementations ksp(project(":tools:noopfactory")) + + testFixturesImplementation(libs.kotlin) + testFixturesImplementation(libs.bundles.jUnit5) + testFixturesImplementation(libs.bundles.testTools) + testFixturesImplementation(project(":tools:unit")) { + attributes { + attribute( + com.android.build.api.attributes.ProductFlavorAttr.of("platform"), + objects.named("jvm") + ) + } + } } kotlinConfig(jvmBytecodeTarget = JvmTarget.JVM_11) diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/TelemetryEvent.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/TelemetryEvent.kt new file mode 100644 index 0000000000..ce4968d284 --- /dev/null +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/TelemetryEvent.kt @@ -0,0 +1,48 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.telemetry + +@Suppress("UndocumentedPublicClass", "UndocumentedPublicFunction", "UndocumentedPublicProperty") +sealed class TelemetryEvent { + + sealed class Log(val message: String, val additionalProperties: Map?) : TelemetryEvent() { + class Debug(message: String, additionalProperties: Map?) : Log(message, additionalProperties) + + class Error( + message: String, + additionalProperties: Map? = null, + val error: Throwable? = null, + val stacktrace: String? = null, + val kind: String? = null + ) : Log(message, additionalProperties) + } + + data class Configuration( + val trackErrors: Boolean, + val batchSize: Long, + val batchUploadFrequency: Long, + val useProxy: Boolean, + val useLocalEncryption: Boolean, + val batchProcessingLevel: Int + ) : TelemetryEvent() + + data class Metric( + val message: String, + val additionalProperties: Map? + ) : TelemetryEvent() + + sealed class ApiUsage(val additionalProperties: MutableMap = mutableMapOf()) : TelemetryEvent() { + class AddViewLoadingTime( + val overwrite: Boolean, + val noView: Boolean, + val noActiveView: Boolean, + additionalProperties: MutableMap = mutableMapOf() + ) : ApiUsage(additionalProperties) + } + + object InterceptorInstantiated : TelemetryEvent() +} diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryApiUsageEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryApiUsageEventForgeryFactory.kt new file mode 100644 index 0000000000..e6aab38029 --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryApiUsageEventForgeryFactory.kt @@ -0,0 +1,23 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.elmyr + +import com.datadog.android.internal.telemetry.TelemetryEvent +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +class TelemetryApiUsageEventForgeryFactory : ForgeryFactory { + + override fun getForgery(forge: Forge): TelemetryEvent.ApiUsage { + return TelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = forge.aBool(), + noView = forge.aBool(), + noActiveView = forge.aBool(), + additionalProperties = forge.aMap { forge.aString() to forge.aString() }.toMutableMap() + ) + } +} diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt new file mode 100644 index 0000000000..721d796661 --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt @@ -0,0 +1,28 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.elmyr + +import com.datadog.android.internal.telemetry.TelemetryEvent +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +class TelemetryEventForgeryFactory : ForgeryFactory { + + override fun getForgery(forge: Forge): TelemetryEvent { + val random = forge.anInt(min = 0, max = 6) + return when (random) { + 0 -> forge.getForgery() + + 1 -> forge.getForgery() + + 2 -> forge.getForgery() + 3 -> TelemetryEvent.InterceptorInstantiated + 4 -> forge.getForgery() + else -> forge.getForgery() + } + } +} diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryInternalConfigurationEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryInternalConfigurationEventForgeryFactory.kt new file mode 100644 index 0000000000..ec1c9fa683 --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryInternalConfigurationEventForgeryFactory.kt @@ -0,0 +1,25 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.elmyr + +import com.datadog.android.internal.telemetry.TelemetryEvent +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +class TelemetryInternalConfigurationEventForgeryFactory : ForgeryFactory { + + override fun getForgery(forge: Forge): TelemetryEvent.Configuration { + return TelemetryEvent.Configuration( + trackErrors = forge.aBool(), + batchSize = forge.aLong(), + batchProcessingLevel = forge.anInt(), + batchUploadFrequency = forge.aLong(), + useProxy = forge.aBool(), + useLocalEncryption = forge.aBool() + ) + } +} diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogDebugEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogDebugEventForgeryFactory.kt new file mode 100644 index 0000000000..2a22a46bf0 --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogDebugEventForgeryFactory.kt @@ -0,0 +1,21 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.elmyr + +import com.datadog.android.internal.telemetry.TelemetryEvent +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +class TelemetryLogDebugEventForgeryFactory : ForgeryFactory { + + override fun getForgery(forge: Forge): TelemetryEvent.Log.Debug { + return TelemetryEvent.Log.Debug( + message = forge.aString(), + additionalProperties = forge.aMap { forge.aString() to forge.aString() } + ) + } +} diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogErrorEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogErrorEventForgeryFactory.kt new file mode 100644 index 0000000000..e06429edd2 --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogErrorEventForgeryFactory.kt @@ -0,0 +1,25 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.elmyr + +import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.tools.unit.forge.aThrowable +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +class TelemetryLogErrorEventForgeryFactory : ForgeryFactory { + + override fun getForgery(forge: Forge): TelemetryEvent.Log.Error { + return TelemetryEvent.Log.Error( + message = forge.aString(), + additionalProperties = forge.aMap { forge.aString() to forge.aString() }, + error = forge.aNullable { forge.aThrowable() }, + stacktrace = forge.aNullable { forge.aString() }, + kind = forge.aNullable { forge.aString() } + ) + } +} diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryMetricEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryMetricEventForgeryFactory.kt new file mode 100644 index 0000000000..54e573e256 --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryMetricEventForgeryFactory.kt @@ -0,0 +1,21 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.elmyr + +import com.datadog.android.internal.telemetry.TelemetryEvent +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +class TelemetryMetricEventForgeryFactory : ForgeryFactory { + + override fun getForgery(forge: Forge): TelemetryEvent.Metric { + return TelemetryEvent.Metric( + message = forge.aString(), + additionalProperties = forge.aMap { forge.aString() to forge.aString() } + ) + } +} diff --git a/features/dd-sdk-android-rum/api/apiSurface b/features/dd-sdk-android-rum/api/apiSurface index e593e97725..63f43e540f 100644 --- a/features/dd-sdk-android-rum/api/apiSurface +++ b/features/dd-sdk-android-rum/api/apiSurface @@ -1712,6 +1712,73 @@ data class com.datadog.android.rum.model.ViewEvent fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): State +data class com.datadog.android.telemetry.model.CommonTelemetryProperties + constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, kotlin.collections.List? = null, Telemetry? = null) + val type: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): CommonTelemetryProperties + fun fromJsonObject(com.google.gson.JsonObject): CommonTelemetryProperties + class Dd + val formatVersion: kotlin.Long + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Dd + fun fromJsonObject(com.google.gson.JsonObject): Dd + data class Application + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + fun fromJsonObject(com.google.gson.JsonObject): Application + data class Session + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Session + fun fromJsonObject(com.google.gson.JsonObject): Session + data class View + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): View + fun fromJsonObject(com.google.gson.JsonObject): View + data class Action + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Action + fun fromJsonObject(com.google.gson.JsonObject): Action + data class Telemetry + constructor(Device? = null, Os? = null, kotlin.collections.MutableMap = mutableMapOf()) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Telemetry + fun fromJsonObject(com.google.gson.JsonObject): Telemetry + data class Device + constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Device + fun fromJsonObject(com.google.gson.JsonObject): Device + data class Os + constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Os + fun fromJsonObject(com.google.gson.JsonObject): Os + enum Source + constructor(kotlin.String) + - ANDROID + - IOS + - BROWSER + - FLUTTER + - REACT_NATIVE + - UNITY + - KOTLIN_MULTIPLATFORM + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Source data class com.datadog.android.telemetry.model.TelemetryConfigurationEvent constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, kotlin.collections.List? = null, Telemetry) val type: kotlin.String @@ -1969,3 +2036,165 @@ data class com.datadog.android.telemetry.model.TelemetryErrorEvent fun toJson(): com.google.gson.JsonElement companion object fun fromJson(kotlin.String): Source +data class com.datadog.android.telemetry.model.TelemetryUsageEvent + constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, kotlin.collections.List? = null, Telemetry) + val type: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TelemetryUsageEvent + fun fromJsonObject(com.google.gson.JsonObject): TelemetryUsageEvent + class Dd + val formatVersion: kotlin.Long + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Dd + fun fromJsonObject(com.google.gson.JsonObject): Dd + data class Application + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Application + fun fromJsonObject(com.google.gson.JsonObject): Application + data class Session + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Session + fun fromJsonObject(com.google.gson.JsonObject): Session + data class View + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): View + fun fromJsonObject(com.google.gson.JsonObject): View + data class Action + constructor(kotlin.String) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Action + fun fromJsonObject(com.google.gson.JsonObject): Action + data class Telemetry + constructor(Device? = null, Os? = null, Usage, kotlin.collections.MutableMap = mutableMapOf()) + val type: kotlin.String + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Telemetry + fun fromJsonObject(com.google.gson.JsonObject): Telemetry + data class Device + constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Device + fun fromJsonObject(com.google.gson.JsonObject): Device + data class Os + constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null) + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Os + fun fromJsonObject(com.google.gson.JsonObject): Os + sealed class Usage + abstract fun toJson(): com.google.gson.JsonElement + data class SetTrackingConsent : Usage + constructor(TrackingConsent) + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): SetTrackingConsent + fun fromJsonObject(com.google.gson.JsonObject): SetTrackingConsent + class StopSession : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): StopSession + fun fromJsonObject(com.google.gson.JsonObject): StopSession + class StartView : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): StartView + fun fromJsonObject(com.google.gson.JsonObject): StartView + class AddAction : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): AddAction + fun fromJsonObject(com.google.gson.JsonObject): AddAction + class AddError : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): AddError + fun fromJsonObject(com.google.gson.JsonObject): AddError + class SetGlobalContext : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): SetGlobalContext + fun fromJsonObject(com.google.gson.JsonObject): SetGlobalContext + class SetUser : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): SetUser + fun fromJsonObject(com.google.gson.JsonObject): SetUser + class AddFeatureFlagEvaluation : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): AddFeatureFlagEvaluation + fun fromJsonObject(com.google.gson.JsonObject): AddFeatureFlagEvaluation + data class TelemetryBrowserFeaturesUsage_0 : Usage + constructor(kotlin.Boolean? = null) + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TelemetryBrowserFeaturesUsage_0 + fun fromJsonObject(com.google.gson.JsonObject): TelemetryBrowserFeaturesUsage_0 + class TelemetryBrowserFeaturesUsage_1 : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TelemetryBrowserFeaturesUsage_1 + fun fromJsonObject(com.google.gson.JsonObject): TelemetryBrowserFeaturesUsage_1 + class TelemetryBrowserFeaturesUsage_2 : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TelemetryBrowserFeaturesUsage_2 + fun fromJsonObject(com.google.gson.JsonObject): TelemetryBrowserFeaturesUsage_2 + class TelemetryBrowserFeaturesUsage_3 : Usage + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TelemetryBrowserFeaturesUsage_3 + fun fromJsonObject(com.google.gson.JsonObject): TelemetryBrowserFeaturesUsage_3 + data class AddViewLoadingTime : Usage + constructor(kotlin.Boolean, kotlin.Boolean, kotlin.Boolean) + val feature: kotlin.String + override fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): AddViewLoadingTime + fun fromJsonObject(com.google.gson.JsonObject): AddViewLoadingTime + companion object + fun fromJson(kotlin.String): Usage + fun fromJsonObject(com.google.gson.JsonObject): Usage + enum Source + constructor(kotlin.String) + - ANDROID + - IOS + - BROWSER + - FLUTTER + - REACT_NATIVE + - UNITY + - KOTLIN_MULTIPLATFORM + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): Source + enum TrackingConsent + constructor(kotlin.String) + - GRANTED + - NOT_GRANTED + - PENDING + fun toJson(): com.google.gson.JsonElement + companion object + fun fromJson(kotlin.String): TrackingConsent diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 9787512860..8443542d0b 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -5150,6 +5150,239 @@ public final class com/datadog/android/sqlite/DatadogDatabaseErrorHandler : andr public fun onCorruption (Landroid/database/sqlite/SQLiteDatabase;)V } +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Companion; + public fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;)V + public synthetic fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; + public final fun component10 ()Ljava/util/List; + public final fun component11 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; + public final fun component2 ()J + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; + public final fun component7 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; + public final fun component8 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; + public final fun component9 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; + public final fun copy (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; + public final fun getAction ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; + public final fun getApplication ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; + public final fun getDate ()J + public final fun getDd ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; + public final fun getExperimentalFeatures ()Ljava/util/List; + public final fun getService ()Ljava/lang/String; + public final fun getSession ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; + public final fun getSource ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public final fun getTelemetry ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; + public final fun getType ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public final fun getView ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Action { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Action$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Application { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Dd { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; + public final fun getFormatVersion ()J + public final fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Device { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; + public final fun getArchitecture ()Ljava/lang/String; + public final fun getBrand ()Ljava/lang/String; + public final fun getModel ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Device$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Os { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; + public final fun getBuild ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Os$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Session { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static final field BROWSER Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source$Companion; + public static final field FLUTTER Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static final field IOS Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static final field REACT_NATIVE Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static final field UNITY Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; + public static fun values ()[Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry$Companion; + public fun ()V + public fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;)V + public synthetic fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; + public final fun component2 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; + public final fun component3 ()Ljava/util/Map; + public final fun copy (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getDevice ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; + public final fun getOs ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$View { + public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$View$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; +} + public final class com/datadog/android/telemetry/model/TelemetryConfigurationEvent { public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Companion; public fun (Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$View;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Telemetry;)V @@ -6164,3 +6397,474 @@ public final class com/datadog/android/telemetry/model/TelemetryErrorEvent$View$ public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryErrorEvent$View; } +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Companion; + public fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;)V + public synthetic fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun component10 ()Ljava/util/List; + public final fun component11 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun component2 ()J + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun component7 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun component8 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public final fun component9 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun copy (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public final fun getAction ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun getApplication ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun getDate ()J + public final fun getDd ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun getExperimentalFeatures ()Ljava/util/List; + public final fun getService ()Ljava/lang/String; + public final fun getSession ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun getSource ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public final fun getTelemetry ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun getType ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public final fun getView ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Action { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Action$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Action; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Application { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Application$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Application; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Dd { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun getFormatVersion ()J + public final fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Dd$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Dd; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Device { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun getArchitecture ()Ljava/lang/String; + public final fun getBrand ()Ljava/lang/String; + public final fun getModel ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Device$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Os { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun getBuild ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Os$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Session { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Session$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Session; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Source : java/lang/Enum { + public static final field ANDROID Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field BROWSER Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source$Companion; + public static final field FLUTTER Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field IOS Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field REACT_NATIVE Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final field UNITY Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; + public static fun values ()[Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Source$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Source; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry$Companion; + public fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;)V + public synthetic fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun component2 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun component3 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public final fun component4 ()Ljava/util/Map; + public final fun copy (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun getAdditionalProperties ()Ljava/util/Map; + public final fun getDevice ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Device; + public final fun getOs ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Os; + public final fun getType ()Ljava/lang/String; + public final fun getUsage ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Telemetry; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent : java/lang/Enum { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent$Companion; + public static final field GRANTED Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static final field NOT_GRANTED Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static final field PENDING Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public final fun toJson ()Lcom/google/gson/JsonElement; + public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public static fun values ()[Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; +} + +public abstract class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Companion; + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public abstract fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddAction; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddError; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddFeatureFlagEvaluation; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime$Companion; + public fun (ZZZ)V + public final fun component1 ()Z + public final fun component2 ()Z + public final fun component3 ()Z + public final fun copy (ZZZ)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime;ZZZILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public final fun getFeature ()Ljava/lang/String; + public final fun getNoActiveView ()Z + public final fun getNoView ()Z + public final fun getOverwritten ()Z + public fun hashCode ()I + public fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$AddViewLoadingTime; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetGlobalContext; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent$Companion; + public fun (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent;)V + public final fun component1 ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public final fun copy (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent;Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public final fun getFeature ()Ljava/lang/String; + public final fun getTrackingConsent ()Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$TrackingConsent; + public fun hashCode ()I + public fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetTrackingConsent; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$SetUser; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StartView; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$StopSession; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0$Companion; + public fun ()V + public fun (Ljava/lang/Boolean;)V + public synthetic fun (Ljava/lang/Boolean;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Boolean; + public final fun copy (Ljava/lang/Boolean;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0;Ljava/lang/Boolean;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public final fun getFeature ()Ljava/lang/String; + public fun hashCode ()I + public final fun isForced ()Ljava/lang/Boolean; + public fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_0; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_1; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_2; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3 : com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3$Companion; + public fun ()V + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; + public final fun getFeature ()Ljava/lang/String; + public fun toJson ()Lcom/google/gson/JsonElement; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$Usage$TelemetryBrowserFeaturesUsage_3; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$View { + public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public fun equals (Ljava/lang/Object;)Z + public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public final fun getId ()Ljava/lang/String; + public fun hashCode ()I + public final fun toJson ()Lcom/google/gson/JsonElement; + public fun toString ()Ljava/lang/String; +} + +public final class com/datadog/android/telemetry/model/TelemetryUsageEvent$View$Companion { + public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; + public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/TelemetryUsageEvent$View; +} + diff --git a/features/dd-sdk-android-rum/build.gradle.kts b/features/dd-sdk-android-rum/build.gradle.kts index 7aeb9c2760..a67682c4bc 100644 --- a/features/dd-sdk-android-rum/build.gradle.kts +++ b/features/dd-sdk-android-rum/build.gradle.kts @@ -71,6 +71,7 @@ dependencies { } } testImplementation(testFixtures(project(":dd-sdk-android-core"))) + testImplementation(testFixtures(project(":dd-sdk-android-internal"))) testImplementation(libs.bundles.jUnit5) testImplementation(libs.bundles.testTools) testImplementation(libs.okHttp) diff --git a/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts b/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts index 26bdaf10b1..9caaaf4c2c 100644 --- a/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts +++ b/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts @@ -15,10 +15,6 @@ tasks.register( ) { inputDirPath = "src/main/json/telemetry" targetPackageName = "com.datadog.android.telemetry.model" - ignoredFiles = arrayOf( - "_common-schema.json", - "usage-schema.json" - ) inputNameMapping = mapOf( "debug-schema.json" to "TelemetryDebugEvent", "error-schema.json" to "TelemetryErrorEvent" diff --git a/features/dd-sdk-android-rum/src/main/json/telemetry/usage-schema.json b/features/dd-sdk-android-rum/src/main/json/telemetry/usage-schema.json index 5b82fc045d..91d9f8387e 100644 --- a/features/dd-sdk-android-rum/src/main/json/telemetry/usage-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/telemetry/usage-schema.json @@ -28,6 +28,9 @@ }, { "$ref": "usage/browser-features-schema.json" + }, + { + "$ref": "usage/mobile-features-schema.json" } ] } diff --git a/features/dd-sdk-android-rum/src/main/json/telemetry/usage/common-features-schema.json b/features/dd-sdk-android-rum/src/main/json/telemetry/usage/common-features-schema.json index c1e8328fd7..b66c784b5e 100644 --- a/features/dd-sdk-android-rum/src/main/json/telemetry/usage/common-features-schema.json +++ b/features/dd-sdk-android-rum/src/main/json/telemetry/usage/common-features-schema.json @@ -7,6 +7,7 @@ "oneOf": [ { "required": ["feature", "tracking_consent"], + "title": "SetTrackingConsent", "properties": { "feature": { "type": "string", @@ -22,6 +23,7 @@ }, { "required": ["feature"], + "title": "StopSession", "properties": { "feature": { "type": "string", @@ -32,6 +34,7 @@ }, { "required": ["feature"], + "title": "StartView", "properties": { "feature": { "type": "string", @@ -42,6 +45,7 @@ }, { "required": ["feature"], + "title": "AddAction", "properties": { "feature": { "type": "string", @@ -52,6 +56,7 @@ }, { "required": ["feature"], + "title": "AddError", "properties": { "feature": { "type": "string", @@ -62,6 +67,7 @@ }, { "required": ["feature"], + "title": "SetGlobalContext", "properties": { "feature": { "type": "string", @@ -72,6 +78,7 @@ }, { "required": ["feature"], + "title": "SetUser", "properties": { "feature": { "type": "string", @@ -82,6 +89,7 @@ }, { "required": ["feature"], + "title": "AddFeatureFlagEvaluation", "properties": { "feature": { "type": "string", diff --git a/features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json b/features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json new file mode 100644 index 0000000000..450fa9acf4 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/json/telemetry/usage/mobile-features-schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "telemetry/usage/mobile-features-schema.json", + "title": "TelemetryMobileFeaturesUsage", + "type": "object", + "description": "Schema of mobile specific features usage", + "oneOf": [ + { + "required": ["feature", "no_view", "no_active_view", "overwritten"], + "title": "AddViewLoadingTime", + "properties": { + "feature": { + "type": "string", + "description": "addViewLoadingTime API", + "const": "addViewLoadingTime" + }, + "no_view": { + "type": "boolean", + "description": "Whether the view is not available" + }, + "no_active_view": { + "type": "boolean", + "description": "Whether the available view is not active" + }, + "overwritten": { + "type": "boolean", + "description": "Whether the loading time was overwritten" + } + } + } + ] +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index c599b09d8c..4fe900bf4d 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -33,6 +33,7 @@ import com.datadog.android.core.internal.utils.submitSafe import com.datadog.android.event.EventMapper import com.datadog.android.event.MapperSerializer import com.datadog.android.event.NoOpEventMapper +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumSessionListener @@ -78,8 +79,6 @@ import com.datadog.android.rum.tracking.NoOpViewTrackingStrategy import com.datadog.android.rum.tracking.TrackingStrategy import com.datadog.android.rum.tracking.ViewAttributesProvider import com.datadog.android.rum.tracking.ViewTrackingStrategy -import com.datadog.android.telemetry.internal.Telemetry -import com.datadog.android.telemetry.internal.TelemetryCoreConfiguration import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import java.util.Locale import java.util.concurrent.ExecutorService @@ -128,7 +127,6 @@ internal class RumFeature( private var anrDetectorExecutorService: ExecutorService? = null internal var anrDetectorRunnable: ANRDetectorRunnable? = null internal lateinit var appContext: Context - internal lateinit var telemetry: Telemetry private val lateCrashEventHandler by lazy { lateCrashReporterFactory(sdkCore as InternalSdkCore) } @@ -138,7 +136,6 @@ internal class RumFeature( override fun onInitialize(appContext: Context) { this.appContext = appContext - this.telemetry = Telemetry(sdkCore) dataWriter = createDataWriter( configuration, @@ -272,11 +269,6 @@ internal class RumFeature( WEB_VIEW_INGESTED_NOTIFICATION_MESSAGE_TYPE -> { (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor)?.sendWebViewEvent() } - - TELEMETRY_ERROR_MESSAGE_TYPE -> logTelemetryError(event) - TELEMETRY_DEBUG_MESSAGE_TYPE -> logTelemetryDebug(event) - MOBILE_METRIC_MESSAGE_TYPE -> logMetric(event) - TELEMETRY_CONFIG_MESSAGE_TYPE -> logTelemetryConfiguration(event) FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE -> { (GlobalRumMonitor.get(sdkCore) as? DatadogRumMonitor)?.let { it.stopKeepAliveCallback() @@ -284,6 +276,8 @@ internal class RumFeature( } } + TELEMETRY_EVENT_MESSAGE_TYPE -> handleTelemetryEvent(event) + else -> { sdkCore.internalLogger.log( InternalLogger.Level.WARN, @@ -298,6 +292,20 @@ internal class RumFeature( // region Internal + private fun handleTelemetryEvent(event: Map<*, *>) { + val telemetryEvent = event[EVENT_MESSAGE_KEY] as? TelemetryEvent + val advancedRumMonitor = GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor ?: return + if (telemetryEvent != null) { + advancedRumMonitor.sendTelemetryEvent(telemetryEvent) + } else { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.MAINTAINER, + { TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE } + ) + } + } + @AnyThread internal fun enableDebugging(advancedRumMonitor: AdvancedRumMonitor) { if (!initialized.get()) { @@ -502,68 +510,6 @@ internal class RumFeature( ) } - private fun logTelemetryError(telemetryEvent: Map<*, *>) { - val message = telemetryEvent[EVENT_MESSAGE_PROPERTY] as? String - if (message == null) { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.MAINTAINER, - { TELEMETRY_MISSING_MESSAGE_FIELD } - ) - return - } - val throwable = telemetryEvent[EVENT_THROWABLE_PROPERTY] as? Throwable - val stack = telemetryEvent[EVENT_STACKTRACE_PROPERTY] as? String - val kind = telemetryEvent["kind"] as? String - - @Suppress("UNCHECKED_CAST") - val additionalProperties = telemetryEvent[EVENT_ADDITIONAL_PROPERTIES] as? Map - if (throwable != null) { - telemetry.error(message, throwable, additionalProperties) - } else { - telemetry.error(message, stack, kind, additionalProperties) - } - } - - private fun logTelemetryDebug(telemetryEvent: Map<*, *>) { - val message = telemetryEvent[EVENT_MESSAGE_PROPERTY] as? String - - @Suppress("UNCHECKED_CAST") - val additionalProperties = telemetryEvent[EVENT_ADDITIONAL_PROPERTIES] as? Map - if (message == null) { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.MAINTAINER, - { TELEMETRY_MISSING_MESSAGE_FIELD } - ) - return - } - telemetry.debug(message, additionalProperties) - } - - private fun logMetric(metricEvent: Map<*, *>) { - val message = metricEvent[EVENT_MESSAGE_PROPERTY] as? String - - @Suppress("UNCHECKED_CAST") - val additionalProperties = metricEvent[EVENT_ADDITIONAL_PROPERTIES] as? Map - if (message == null) { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.MAINTAINER, - { TELEMETRY_MISSING_MESSAGE_FIELD } - ) - return - } - telemetry.metric(message, additionalProperties) - } - - private fun logTelemetryConfiguration(event: Map<*, *>) { - TelemetryCoreConfiguration.fromEvent(event, sdkCore.internalLogger)?.let { - (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) - ?.sendConfigurationTelemetryEvent(it) - } - } - // endregion internal data class Configuration( @@ -596,10 +542,7 @@ internal class RumFeature( internal const val LOGGER_ERROR_BUS_MESSAGE_TYPE = "logger_error" internal const val LOGGER_ERROR_WITH_STACK_TRACE_MESSAGE_TYPE = "logger_error_with_stacktrace" internal const val WEB_VIEW_INGESTED_NOTIFICATION_MESSAGE_TYPE = "web_view_ingested_notification" - internal const val TELEMETRY_ERROR_MESSAGE_TYPE = "telemetry_error" - internal const val TELEMETRY_DEBUG_MESSAGE_TYPE = "telemetry_debug" internal const val MOBILE_METRIC_MESSAGE_TYPE = "mobile_metric" - internal const val TELEMETRY_CONFIG_MESSAGE_TYPE = "telemetry_configuration" internal const val FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE = "flush_and_stop_monitor" internal const val ALL_IN_SAMPLE_RATE: Float = 100f @@ -637,10 +580,11 @@ internal class RumFeature( ) internal const val EVENT_MESSAGE_PROPERTY = "message" - internal const val EVENT_ADDITIONAL_PROPERTIES = "additionalProperties" internal const val EVENT_THROWABLE_PROPERTY = "throwable" internal const val EVENT_ATTRIBUTES_PROPERTY = "attributes" internal const val EVENT_STACKTRACE_PROPERTY = "stacktrace" + internal const val TELEMETRY_EVENT_MESSAGE_TYPE = "telemetry_event" + internal const val EVENT_MESSAGE_KEY = "event" internal const val UNSUPPORTED_EVENT_TYPE = "RUM feature receive an event of unsupported type=%s." @@ -656,8 +600,8 @@ internal class RumFeature( internal const val LOG_ERROR_WITH_STACKTRACE_EVENT_MISSING_MANDATORY_FIELDS = "RUM feature received a log event with stacktrace" + " where mandatory message field is either missing or has a wrong type." - internal const val TELEMETRY_MISSING_MESSAGE_FIELD = "RUM feature received a telemetry" + - " event, but mandatory message field is either missing or has a wrong type." + internal const val TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE = "RUM feature received a telemetry" + + " event, but mandatory event field is either missing or has a wrong type." internal const val DEVELOPER_MODE_SAMPLE_RATE_CHANGED_MESSAGE = "Developer mode enabled, setting RUM sample rate to 100%." internal const val RUM_FEATURE_NOT_YET_INITIALIZED = diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt index 87c390623a..a6f29b0467 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt @@ -7,6 +7,7 @@ package com.datadog.android.rum.internal.domain.scope import com.datadog.android.core.feature.event.ThreadDump +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumPerformanceMetric @@ -15,8 +16,6 @@ import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.domain.event.ResourceTiming -import com.datadog.android.telemetry.internal.TelemetryCoreConfiguration -import com.datadog.android.telemetry.internal.TelemetryType internal sealed class RumRawEvent { @@ -217,16 +216,9 @@ internal sealed class RumRawEvent { internal data class WebViewEvent(override val eventTime: Time = Time()) : RumRawEvent() - internal data class SendTelemetry( - val type: TelemetryType, - val message: String, - val stack: String?, - val kind: String?, - val coreConfiguration: TelemetryCoreConfiguration?, - val additionalProperties: Map?, - val onlyOnce: Boolean = false, - override val eventTime: Time = Time(), - val isMetric: Boolean = false + internal data class TelemetryEventWrapper( + val event: TelemetryEvent, + override val eventTime: Time = Time() ) : RumRawEvent() internal data class SdkInit( diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt index 272ccaa75c..585d1ff414 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt @@ -7,11 +7,11 @@ package com.datadog.android.rum.internal.monitor import com.datadog.android.core.feature.event.ThreadDump +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumMonitor import com.datadog.android.rum.RumPerformanceMetric import com.datadog.android.rum.internal.debug.RumDebugListener -import com.datadog.android.telemetry.internal.TelemetryCoreConfiguration import com.datadog.tools.annotation.NoOpImplementation /** @@ -42,31 +42,7 @@ internal interface AdvancedRumMonitor : RumMonitor, AdvancedNetworkRumMonitor { fun setDebugListener(listener: RumDebugListener?) - fun sendDebugTelemetryEvent( - message: String, - additionalProperties: Map? = null - ) - - fun sendErrorTelemetryEvent( - message: String, - throwable: Throwable?, - additionalProperties: Map? = null - ) - - fun sendErrorTelemetryEvent( - message: String, - stack: String?, - kind: String?, - additionalProperties: Map? = null - ) - - fun sendMetricEvent( - message: String, - additionalProperties: Map? - ) - - @Suppress("FunctionMaxLength") - fun sendConfigurationTelemetryEvent(coreConfiguration: TelemetryCoreConfiguration) + fun sendTelemetryEvent(telemetryEvent: TelemetryEvent) fun updatePerformanceMetric(metric: RumPerformanceMetric, value: Double) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index a7b3bb77df..3896153622 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -14,8 +14,8 @@ import com.datadog.android.api.storage.DataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver -import com.datadog.android.core.internal.utils.loggableStackTrace import com.datadog.android.core.internal.utils.submitSafe +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumActionType @@ -45,9 +45,7 @@ import com.datadog.android.rum.internal.domain.scope.RumViewScope import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.resource.ResourceId -import com.datadog.android.telemetry.internal.TelemetryCoreConfiguration import com.datadog.android.telemetry.internal.TelemetryEventHandler -import com.datadog.android.telemetry.internal.TelemetryType import java.util.Locale import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CountDownLatch @@ -587,97 +585,9 @@ internal class DatadogRumMonitor( debugListener = listener } - override fun sendDebugTelemetryEvent( - message: String, - additionalProperties: Map? - ) { - handleEvent( - RumRawEvent.SendTelemetry( - type = TelemetryType.DEBUG, - message = message, - stack = null, - kind = null, - coreConfiguration = null, - additionalProperties = additionalProperties - ) - ) - } - - override fun sendMetricEvent(message: String, additionalProperties: Map?) { - handleEvent( - RumRawEvent.SendTelemetry( - type = TelemetryType.DEBUG, - message = message, - stack = null, - kind = null, - coreConfiguration = null, - additionalProperties = additionalProperties, - isMetric = true - ) - ) - } - - override fun sendErrorTelemetryEvent( - message: String, - throwable: Throwable?, - additionalProperties: Map? - ) { - val stack: String? = throwable?.loggableStackTrace() - val kind: String? = throwable?.javaClass?.canonicalName ?: throwable?.javaClass?.simpleName - handleEvent( - RumRawEvent.SendTelemetry( - type = TelemetryType.ERROR, - message = message, - stack = stack, - kind = kind, - coreConfiguration = null, - additionalProperties = additionalProperties - ) - ) - } - - override fun sendErrorTelemetryEvent( - message: String, - stack: String?, - kind: String?, - additionalProperties: Map? - ) { - handleEvent( - RumRawEvent.SendTelemetry( - type = TelemetryType.ERROR, - message = message, - stack = stack, - kind = kind, - coreConfiguration = null, - additionalProperties = additionalProperties - ) - ) - } - - @Suppress("FunctionMaxLength") - override fun sendConfigurationTelemetryEvent(coreConfiguration: TelemetryCoreConfiguration) { - handleEvent( - RumRawEvent.SendTelemetry( - type = TelemetryType.CONFIGURATION, - message = "", - stack = null, - kind = null, - coreConfiguration = coreConfiguration, - additionalProperties = null - ) - ) - } - override fun notifyInterceptorInstantiated() { handleEvent( - RumRawEvent.SendTelemetry( - TelemetryType.INTERCEPTOR_SETUP, - message = "", - stack = null, - kind = null, - coreConfiguration = null, - additionalProperties = null - ) + RumRawEvent.TelemetryEventWrapper(TelemetryEvent.InterceptorInstantiated) ) } @@ -696,6 +606,10 @@ internal class DatadogRumMonitor( return internalProxy } + override fun sendTelemetryEvent(telemetryEvent: TelemetryEvent) { + handleEvent(RumRawEvent.TelemetryEventWrapper(telemetryEvent)) + } + // endregion // region Internal @@ -720,7 +634,7 @@ internal class DatadogRumMonitor( @Suppress("ThreadSafety") // Crash handling, can't delegate to another thread rootScope.handleEvent(event, writer) } - } else if (event is RumRawEvent.SendTelemetry) { + } else if (event is RumRawEvent.TelemetryEventWrapper) { telemetryEventHandler.handleEvent(event, writer) } else { handler.removeCallbacks(keepAliveRunnable) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/Telemetry.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/Telemetry.kt deleted file mode 100644 index 9100732a71..0000000000 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/Telemetry.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.telemetry.internal - -import com.datadog.android.api.SdkCore -import com.datadog.android.rum.GlobalRumMonitor -import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor -import com.datadog.android.rum.internal.monitor.NoOpAdvancedRumMonitor - -internal class Telemetry( - private val sdkCore: SdkCore -) { - - internal val rumMonitor: AdvancedRumMonitor - get() { - return GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor ?: NoOpAdvancedRumMonitor() - } - - fun error( - message: String, - throwable: Throwable? = null, - additionalProperties: Map? = null - ) { - (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) - ?.sendErrorTelemetryEvent(message, throwable, additionalProperties) - } - - fun error( - message: String, - stack: String? = null, - kind: String? = null, - additionalProperties: Map? = null - ) { - (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) - ?.sendErrorTelemetryEvent(message, stack, kind, additionalProperties) - } - - fun debug(message: String, additionalProperties: Map? = null) { - (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) - ?.sendDebugTelemetryEvent(message, additionalProperties) - } - - fun metric(message: String, additionalProperties: Map? = null) { - (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) - ?.sendMetricEvent(message, additionalProperties) - } -} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfiguration.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfiguration.kt deleted file mode 100644 index 173ce3d49d..0000000000 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfiguration.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.telemetry.internal - -import com.datadog.android.api.InternalLogger - -internal data class TelemetryCoreConfiguration( - val trackErrors: Boolean, - // batchSize.windowDurationMs - val batchSize: Long, - // uploadFrequency.baseStepMs - val batchUploadFrequency: Long, - val useProxy: Boolean, - val useLocalEncryption: Boolean, - // batchProcessingLevel.maxBatchesPerUploadJob - val batchProcessingLevel: Int -) { - companion object { - fun fromEvent(event: Map<*, *>, internalLogger: InternalLogger): TelemetryCoreConfiguration? { - val trackErrors = event["track_errors"] as? Boolean - val batchSize = event["batch_size"] as? Long - val batchUploadFrequency = event["batch_upload_frequency"] as? Long - val useProxy = event["use_proxy"] as? Boolean - val useLocalEncryption = event["use_local_encryption"] as? Boolean - val batchProcessingLevel = event["batch_processing_level"] as? Int - - @Suppress("ComplexCondition") - if (trackErrors == null || batchSize == null || batchUploadFrequency == null || - useProxy == null || useLocalEncryption == null || batchProcessingLevel == null - ) { - // TODO RUM-375 Do an intelligent reporting when message values are missing/have - // wrong type, reporting the parameter name and what is exactly wrong - // this applies to all messages going through the message bus - internalLogger.log( - InternalLogger.Level.ERROR, - InternalLogger.Target.USER, - { - "One of the mandatory parameters for core configuration telemetry" + - " reporting is either missing or have a wrong type." - } - ) - return null - } - - return TelemetryCoreConfiguration( - trackErrors = trackErrors, - batchSize = batchSize, - batchUploadFrequency = batchUploadFrequency, - useProxy = useProxy, - useLocalEncryption = useLocalEncryption, - batchProcessingLevel = batchProcessingLevel - ) - } - } -} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventExt.kt index c694758015..8b15737dc6 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventExt.kt @@ -10,6 +10,7 @@ import com.datadog.android.api.InternalLogger import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import java.util.Locale internal fun TelemetryDebugEvent.Source.Companion.tryFromSource( @@ -46,6 +47,23 @@ internal fun TelemetryErrorEvent.Source.Companion.tryFromSource( } } +internal fun TelemetryUsageEvent.Source.Companion.tryFromSource( + source: String, + internalLogger: InternalLogger +): TelemetryUsageEvent.Source? { + return try { + fromJson(source) + } catch (e: NoSuchElementException) { + internalLogger.log( + InternalLogger.Level.ERROR, + InternalLogger.Target.USER, + { UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT.format(Locale.US, source) }, + e + ) + null + } +} + internal fun TelemetryConfigurationEvent.Source.Companion.tryFromSource( source: String, internalLogger: InternalLogger diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index cc31b50884..8464d1262e 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -15,6 +15,7 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.RateBasedSampler import com.datadog.android.core.sampling.Sampler +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.domain.RumContext @@ -27,9 +28,11 @@ import com.datadog.android.rum.tracking.NavigationViewTrackingStrategy import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import java.util.Locale import com.datadog.android.telemetry.model.TelemetryConfigurationEvent.ViewTrackingStrategy as VTS +@Suppress("TooManyFunctions") internal class TelemetryEventHandler( internal val sdkCore: InternalSdkCore, internal val eventSampler: Sampler, @@ -44,19 +47,29 @@ internal class TelemetryEventHandler( private var totalEventsSeenInCurrentSession = 0 @AnyThread + @Suppress("LongMethod") fun handleEvent( - event: RumRawEvent.SendTelemetry, + wrappedEvent: RumRawEvent.TelemetryEventWrapper, writer: DataWriter ) { + val event = wrappedEvent.event if (!canWrite(event)) return eventIDsSeenInCurrentSession.add(event.identity) totalEventsSeenInCurrentSession++ - sdkCore.getFeature(Feature.RUM_FEATURE_NAME)?.withWriteContext { datadogContext, eventBatchWriter -> - val timestamp = event.eventTime.timestamp + datadogContext.time.serverTimeOffsetMs - val telemetryEvent: Any? = when (event.type) { - TelemetryType.DEBUG -> { + val timestamp = wrappedEvent.eventTime.timestamp + datadogContext.time.serverTimeOffsetMs + val telemetryEvent: Any? = when (event) { + is TelemetryEvent.Log.Debug -> { + createDebugEvent( + datadogContext = datadogContext, + timestamp = timestamp, + message = event.message, + additionalProperties = event.additionalProperties + ) + } + + is TelemetryEvent.Metric -> { createDebugEvent( datadogContext = datadogContext, timestamp = timestamp, @@ -65,7 +78,7 @@ internal class TelemetryEventHandler( ) } - TelemetryType.ERROR -> { + is TelemetryEvent.Log.Error -> { sessionEndedMetricDispatcher.onSdkErrorTracked( sessionId = datadogContext.rumContext().sessionId, errorKind = event.kind @@ -74,38 +87,35 @@ internal class TelemetryEventHandler( datadogContext = datadogContext, timestamp = timestamp, message = event.message, - stack = event.stack, + stack = event.stacktrace, kind = event.kind, additionalProperties = event.additionalProperties ) } - TelemetryType.CONFIGURATION -> { - val coreConfiguration = event.coreConfiguration - if (coreConfiguration == null) { - createErrorEvent( - datadogContext = datadogContext, - timestamp = timestamp, - message = "Trying to send configuration event with null config", - stack = null, - kind = null, - additionalProperties = null - ) - } else { - createConfigurationEvent( - datadogContext, - timestamp, - coreConfiguration - ) - } + is TelemetryEvent.Configuration -> { + createConfigurationEvent( + datadogContext, + timestamp, + event + ) } - TelemetryType.INTERCEPTOR_SETUP -> { + is TelemetryEvent.ApiUsage -> { + createApiUsageEvent( + datadogContext = datadogContext, + timestamp = timestamp, + event = event + ) + } + + is TelemetryEvent.InterceptorInstantiated -> { trackNetworkRequests = true null } - } + else -> null + } if (telemetryEvent != null) { writer.write(eventBatchWriter, telemetryEvent, EventType.TELEMETRY) } @@ -120,16 +130,16 @@ internal class TelemetryEventHandler( // region private @Suppress("ReturnCount") - private fun canWrite(event: RumRawEvent.SendTelemetry): Boolean { + private fun canWrite(event: TelemetryEvent): Boolean { if (!eventSampler.sample()) return false - if (event.type == TelemetryType.CONFIGURATION && !configurationExtraSampler.sample()) { + if (event is TelemetryEvent.Configuration && !configurationExtraSampler.sample()) { return false } val eventIdentity = event.identity - if (!event.isMetric && eventIDsSeenInCurrentSession.contains(eventIdentity)) { + if (event !is TelemetryEvent.Metric && eventIDsSeenInCurrentSession.contains(eventIdentity)) { sdkCore.internalLogger.log( InternalLogger.Level.INFO, InternalLogger.Target.MAINTAINER, @@ -243,7 +253,7 @@ internal class TelemetryEventHandler( private fun createConfigurationEvent( datadogContext: DatadogContext, timestamp: Long, - coreConfiguration: TelemetryCoreConfiguration + event: TelemetryEvent.Configuration ): TelemetryConfigurationEvent { val traceFeature = sdkCore.getFeature(Feature.TRACING_FEATURE_NAME) val sessionReplayFeatureContext = @@ -302,16 +312,16 @@ internal class TelemetryEventHandler( configuration = TelemetryConfigurationEvent.Configuration( sessionSampleRate = rumConfig?.sampleRate?.toLong(), telemetrySampleRate = rumConfig?.telemetrySampleRate?.toLong(), - useProxy = coreConfiguration.useProxy, + useProxy = event.useProxy, trackFrustrations = rumConfig?.trackFrustrations, - useLocalEncryption = coreConfiguration.useLocalEncryption, + useLocalEncryption = event.useLocalEncryption, viewTrackingStrategy = viewTrackingStrategy, trackBackgroundEvents = rumConfig?.backgroundEventTracking, trackInteractions = rumConfig?.userActionTracking != null, - trackErrors = coreConfiguration.trackErrors, + trackErrors = event.trackErrors, trackNativeLongTasks = rumConfig?.longTaskTrackingStrategy != null, - batchSize = coreConfiguration.batchSize, - batchUploadFrequency = coreConfiguration.batchUploadFrequency, + batchSize = event.batchSize, + batchUploadFrequency = event.batchUploadFrequency, mobileVitalsUpdatePeriod = rumConfig?.vitalsMonitorUpdateFrequency?.periodInMs, useTracing = useTracing, tracerApi = tracerApi?.name, @@ -322,12 +332,57 @@ internal class TelemetryEventHandler( touchPrivacyLevel = sessionReplayTouchPrivacy, textAndInputPrivacyLevel = sessionReplayTextAndInputPrivacy, startRecordingImmediately = startRecordingImmediately, - batchProcessingLevel = coreConfiguration.batchProcessingLevel.toLong() + batchProcessingLevel = event.batchProcessingLevel.toLong() ) ) ) } + private fun createApiUsageEvent( + datadogContext: DatadogContext, + timestamp: Long, + event: TelemetryEvent.ApiUsage + ): TelemetryUsageEvent? { + val rumContext = datadogContext.rumContext() + return when (event) { + is TelemetryEvent.ApiUsage.AddViewLoadingTime -> { + TelemetryUsageEvent( + dd = TelemetryUsageEvent.Dd(), + date = timestamp, + source = TelemetryUsageEvent.Source.tryFromSource( + datadogContext.source, + sdkCore.internalLogger + ) ?: TelemetryUsageEvent.Source.ANDROID, + service = datadogContext.service, + version = datadogContext.sdkVersion, + application = TelemetryUsageEvent.Application(rumContext.applicationId), + session = TelemetryUsageEvent.Session(rumContext.sessionId), + view = rumContext.viewId?.let { TelemetryUsageEvent.View(it) }, + action = rumContext.actionId?.let { TelemetryUsageEvent.Action(it) }, + telemetry = TelemetryUsageEvent.Telemetry( + additionalProperties = event.additionalProperties, + device = TelemetryUsageEvent.Device( + architecture = datadogContext.deviceInfo.architecture, + brand = datadogContext.deviceInfo.deviceBrand, + model = datadogContext.deviceInfo.deviceModel + ), + os = TelemetryUsageEvent.Os( + build = datadogContext.deviceInfo.deviceBuildId, + version = datadogContext.deviceInfo.osVersion, + name = datadogContext.deviceInfo.osName + ), + usage = TelemetryUsageEvent.Usage.AddViewLoadingTime( + overwritten = event.overwrite, + noView = event.noView, + noActiveView = event.noActiveView + ) + ) + ) + } + else -> null + } + } + private fun isGlobalTracerRegistered(): Boolean { // We don't reference io.opentracing from RUM directly, so using reflection for this. // Would be nice to add the test with the flavor which is has no io.opentracing and test diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt index d4387f1960..cce1077eb5 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt @@ -6,7 +6,7 @@ package com.datadog.android.telemetry.internal -import com.datadog.android.rum.internal.domain.scope.RumRawEvent +import com.datadog.android.internal.telemetry.TelemetryEvent internal data class TelemetryEventId( val type: TelemetryType, @@ -14,7 +14,22 @@ internal data class TelemetryEventId( val kind: String? ) -internal val RumRawEvent.SendTelemetry.identity: TelemetryEventId +internal val TelemetryEvent.identity: TelemetryEventId get() { - return TelemetryEventId(type, message, kind) + return when (this) { + is TelemetryEvent.Log.Error -> TelemetryEventId(type(), message, kind) + is TelemetryEvent.Log.Debug -> TelemetryEventId(type(), message, null) + else -> TelemetryEventId(type(), "", null) + } } + +internal fun TelemetryEvent.type(): TelemetryType { + return when (this) { + is TelemetryEvent.Log.Debug -> TelemetryType.DEBUG + is TelemetryEvent.Log.Error -> TelemetryType.ERROR + is TelemetryEvent.Configuration -> TelemetryType.CONFIGURATION + is TelemetryEvent.Metric -> TelemetryType.METRIC + is TelemetryEvent.ApiUsage -> TelemetryType.API_USAGE + is TelemetryEvent.InterceptorInstantiated -> TelemetryType.INTERCEPTOR_SETUP + } +} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt index b1b1d4f11a..860346b451 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryType.kt @@ -10,5 +10,7 @@ internal enum class TelemetryType { DEBUG, ERROR, CONFIGURATION, - INTERCEPTOR_SETUP + INTERCEPTOR_SETUP, + API_USAGE, + METRIC } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 671a0a1071..1f7dcc8a66 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -19,6 +19,7 @@ import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.event.EventMapper import com.datadog.android.event.MapperSerializer +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.assertj.RumFeatureAssert @@ -45,7 +46,6 @@ import com.datadog.android.rum.utils.config.ApplicationContextTestConfiguration import com.datadog.android.rum.utils.config.MainLooperTestConfiguration import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.rum.utils.verifyLog -import com.datadog.android.telemetry.internal.TelemetryCoreConfiguration import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.annotations.TestTargetApi import com.datadog.tools.unit.extensions.ApiLevelExtension @@ -57,7 +57,6 @@ import com.datadog.tools.unit.forge.exhaustiveAttributes import com.datadog.tools.unit.getFieldValue import com.google.gson.JsonObject import fr.xgouchet.elmyr.Forge -import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery @@ -1175,6 +1174,8 @@ internal class RumFeatureTest { // endregion + // region FeatureEventReceiver#onReceive + webview notification + @Test fun `M notify webview event received W onReceive() {webview event received}`() { // Given @@ -1191,78 +1192,35 @@ internal class RumFeatureTest { verifyNoInteractions(mockInternalLogger) } - // region FeatureEventReceiver#onReceive + telemetry event - - @Test - fun `M handle telemetry debug event W onReceive(){no additionalProperties}`( - @StringForgery fakeMessage: String - ) { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val event = mapOf( - "type" to "telemetry_debug", - "message" to fakeMessage - ) - - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor).sendDebugTelemetryEvent(fakeMessage, null) - verifyNoMoreInteractions(mockRumMonitor) - verifyNoInteractions(mockInternalLogger) - } - - @Test - fun `M handle telemetry debug event W onReceive() {additionalProperties}`( - @StringForgery fakeMessage: String, - forge: Forge - ) { - // Given - val fakeAdditionalProperties = forge.exhaustiveAttributes() - testedFeature.onInitialize(appContext.mockInstance) - val event = mapOf( - "type" to "telemetry_debug", - "message" to fakeMessage, - "additionalProperties" to fakeAdditionalProperties - ) - - // When - testedFeature.onReceive(event) + // endregion - // Then - verify(mockRumMonitor).sendDebugTelemetryEvent(fakeMessage, fakeAdditionalProperties) - verifyNoMoreInteractions(mockRumMonitor) - verifyNoInteractions(mockInternalLogger) - } + // region FeatureEventReceiver#onReceive + telemetry event @Test - fun `M handle telemetry debug event W onReceive {additionalProperties is null}`( - @StringForgery fakeMessage: String + fun `M handle telemetry event W onReceive()`( + @Forgery fakeTelemetryEvent: TelemetryEvent ) { // Given - val fakeAdditionalProperties = null testedFeature.onInitialize(appContext.mockInstance) val event = mapOf( - "type" to "telemetry_debug", - "message" to fakeMessage, - "additionalProperties" to fakeAdditionalProperties + "type" to RumFeature.TELEMETRY_EVENT_MESSAGE_TYPE, + "event" to fakeTelemetryEvent ) // When testedFeature.onReceive(event) // Then - verify(mockRumMonitor).sendDebugTelemetryEvent(fakeMessage, fakeAdditionalProperties) + verify(mockRumMonitor).sendTelemetryEvent(fakeTelemetryEvent) verifyNoMoreInteractions(mockRumMonitor) verifyNoInteractions(mockInternalLogger) } @Test - fun `M log warning W onReceive() { telemetry debug + message is missing }`() { + fun `M log warning W onReceive() { telemetry event, event is missing }`() { // Given val event = mapOf( - "type" to "telemetry_debug" + "type" to RumFeature.TELEMETRY_EVENT_MESSAGE_TYPE ) // When @@ -1272,188 +1230,18 @@ internal class RumFeatureTest { mockInternalLogger.verifyLog( InternalLogger.Level.WARN, InternalLogger.Target.MAINTAINER, - RumFeature.TELEMETRY_MISSING_MESSAGE_FIELD - ) - - verifyNoInteractions(mockRumMonitor) - } - - @Test - fun `M handle telemetry error event W onReceive() { with throwable, no additional properties }`( - @StringForgery fakeMessage: String, - forge: Forge - ) { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val fakeThrowable = forge.aThrowable() - val event = mapOf( - "type" to "telemetry_error", - "message" to fakeMessage, - "throwable" to fakeThrowable - ) - - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor) - .sendErrorTelemetryEvent(fakeMessage, fakeThrowable) - verifyNoMoreInteractions(mockRumMonitor) - verifyNoInteractions(mockInternalLogger) - } - - @Test - fun `M handle telemetry error event W onReceive() { with throwable, with additional properties }`( - @StringForgery fakeMessage: String, - forge: Forge - ) { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val fakeThrowable = forge.aThrowable() - val fakeAdditionalProperties = forge.exhaustiveAttributes() - val event = mapOf( - "type" to "telemetry_error", - "message" to fakeMessage, - "throwable" to fakeThrowable, - "additionalProperties" to fakeAdditionalProperties - ) - - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor) - .sendErrorTelemetryEvent(fakeMessage, fakeThrowable, fakeAdditionalProperties) - verifyNoMoreInteractions(mockRumMonitor) - verifyNoInteractions(mockInternalLogger) - } - - @Test - fun `M handle telemetry error event W onReceive() { with stack and kind, no additional properties }`( - @StringForgery fakeMessage: String, - forge: Forge - ) { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val fakeStack = forge.aNullable { aString() } - val fakeKind = forge.aNullable { aString() } - val event = mapOf( - "type" to "telemetry_error", - "message" to fakeMessage, - "stacktrace" to fakeStack, - "kind" to fakeKind - ) - - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor) - .sendErrorTelemetryEvent(fakeMessage, fakeStack, fakeKind) - - verifyNoMoreInteractions(mockRumMonitor) - - verifyNoInteractions(mockInternalLogger) - } - - @Test - fun `M handle telemetry error event W onReceive() { with stack and kind, with additional properties }`( - @StringForgery fakeMessage: String, - forge: Forge - ) { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val fakeStack = forge.aNullable { aString() } - val fakeKind = forge.aNullable { aString() } - val fakeAdditionalProperties = forge.exhaustiveAttributes() - val event = mapOf( - "type" to "telemetry_error", - "message" to fakeMessage, - "stacktrace" to fakeStack, - "kind" to fakeKind, - "additionalProperties" to fakeAdditionalProperties - ) - - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor) - .sendErrorTelemetryEvent(fakeMessage, fakeStack, fakeKind, fakeAdditionalProperties) - - verifyNoMoreInteractions(mockRumMonitor) - - verifyNoInteractions(mockInternalLogger) - } - - @Test - fun `M handle metric event W onReceive()`( - @StringForgery fakeMessage: String, - forge: Forge - ) { - // Given - val fakeAdditionalProperties = forge.exhaustiveAttributes() - testedFeature.onInitialize(appContext.mockInstance) - val event = mapOf( - "type" to RumFeature.MOBILE_METRIC_MESSAGE_TYPE, - "message" to fakeMessage, - "additionalProperties" to fakeAdditionalProperties + RumFeature.TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE ) - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor).sendMetricEvent(fakeMessage, fakeAdditionalProperties) - verifyNoMoreInteractions(mockRumMonitor) - verifyNoInteractions(mockInternalLogger) - } - - @Test - fun `M handle metric event W onReceive(){no additionalProperties}`( - @StringForgery fakeMessage: String - ) { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val event = mapOf( - "type" to RumFeature.MOBILE_METRIC_MESSAGE_TYPE, - "message" to fakeMessage - ) - - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor).sendMetricEvent(fakeMessage, null) - verifyNoMoreInteractions(mockRumMonitor) - verifyNoInteractions(mockInternalLogger) - } - - @Test - fun `M handle metric event W onReceive(){no message}`() { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val event = mapOf( - "type" to RumFeature.MOBILE_METRIC_MESSAGE_TYPE - ) - - // When - testedFeature.onReceive(event) - - // Then - mockInternalLogger.verifyLog( - InternalLogger.Level.WARN, - InternalLogger.Target.MAINTAINER, - RumFeature.TELEMETRY_MISSING_MESSAGE_FIELD - ) verifyNoInteractions(mockRumMonitor) } @Test - fun `M log warning W onReceive() { telemetry error + message is missing }`() { + fun `M log warning W onReceive() { telemetry event, event is not of type Telemetry }`() { // Given val event = mapOf( - "type" to "telemetry_error" + "type" to RumFeature.TELEMETRY_EVENT_MESSAGE_TYPE, + "event" to Any() ) // When @@ -1463,52 +1251,12 @@ internal class RumFeatureTest { mockInternalLogger.verifyLog( InternalLogger.Level.WARN, InternalLogger.Target.MAINTAINER, - RumFeature.TELEMETRY_MISSING_MESSAGE_FIELD + RumFeature.TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE ) verifyNoInteractions(mockRumMonitor) } - @Test - fun `M submit configuration telemetry W onReceive() { telemetry configuration }`( - @BoolForgery trackErrors: Boolean, - @BoolForgery useProxy: Boolean, - @BoolForgery useLocalEncryption: Boolean, - @LongForgery(min = 0L) batchSize: Long, - @LongForgery(min = 0L) batchUploadFrequency: Long, - @IntForgery(min = 0) batchProcessingLevel: Int - ) { - // Given - testedFeature.onInitialize(appContext.mockInstance) - val event = mapOf( - "type" to "telemetry_configuration", - "track_errors" to trackErrors, - "batch_size" to batchSize, - "batch_upload_frequency" to batchUploadFrequency, - "use_proxy" to useProxy, - "use_local_encryption" to useLocalEncryption, - "batch_processing_level" to batchProcessingLevel - ) - - // When - testedFeature.onReceive(event) - - // Then - verify(mockRumMonitor) - .sendConfigurationTelemetryEvent( - TelemetryCoreConfiguration( - trackErrors = trackErrors, - batchSize = batchSize, - batchUploadFrequency = batchUploadFrequency, - useProxy = useProxy, - useLocalEncryption = useLocalEncryption, - batchProcessingLevel = batchProcessingLevel - ) - ) - - verifyNoInteractions(mockInternalLogger) - } - // endregion private fun Forge.anApplicationExitInfoList( diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt index b374028b53..326945cd73 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt @@ -9,6 +9,7 @@ package com.datadog.android.rum.internal.metric import com.datadog.android.api.InternalLogger import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType +import com.datadog.android.internal.telemetry.TelemetryEvent class FakeInternalLogger : InternalLogger { @@ -51,4 +52,8 @@ class FakeInternalLogger : InternalLogger { // do nothing return null } + + override fun logApiUsage(apiUsageEvent: TelemetryEvent.ApiUsage, samplingRate: Float) { + // do nothing + } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index e0e50371fe..c69310ec06 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -16,7 +16,7 @@ import com.datadog.android.api.storage.DataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver -import com.datadog.android.core.internal.utils.loggableStackTrace +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumActionType @@ -45,9 +45,7 @@ import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.resource.ResourceId import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.rum.utils.verifyLog -import com.datadog.android.telemetry.internal.TelemetryCoreConfiguration import com.datadog.android.telemetry.internal.TelemetryEventHandler -import com.datadog.android.telemetry.internal.TelemetryType import com.datadog.tools.unit.forge.aThrowable import com.datadog.tools.unit.forge.exhaustiveAttributes import fr.xgouchet.elmyr.Forge @@ -1862,177 +1860,17 @@ internal class DatadogRumMonitorTest { } @Test - fun `M handle debug telemetry event W sendDebugTelemetryEvent()`( - @StringForgery message: String, - forge: Forge - ) { - // Given - val fakeAdditionalProperties = forge.aNullable { exhaustiveAttributes() } - - // When - testedMonitor.sendDebugTelemetryEvent(message, fakeAdditionalProperties) - - // Then - argumentCaptor { - verify(mockTelemetryEventHandler).handleEvent( - capture(), - eq(mockWriter) - ) - assertThat(lastValue.message).isEqualTo(message) - assertThat(lastValue.type).isEqualTo(TelemetryType.DEBUG) - assertThat(lastValue.stack).isNull() - assertThat(lastValue.kind).isNull() - assertThat(lastValue.coreConfiguration).isNull() - assertThat(lastValue.isMetric).isFalse - assertThat(lastValue.additionalProperties).isEqualTo(fakeAdditionalProperties) - } - } - - @Test - fun `M handle error telemetry event W sendErrorTelemetryEvent() {stack+kind}`( - @StringForgery message: String, - @StringForgery stackTrace: String, - @StringForgery kind: String, - forge: Forge - ) { - // Given - val fakeAdditionalProperties = forge.aNullable { exhaustiveAttributes() } - - // When - testedMonitor.sendErrorTelemetryEvent(message, stackTrace, kind, fakeAdditionalProperties) - - // Then - argumentCaptor { - verify(mockTelemetryEventHandler).handleEvent( - capture(), - eq(mockWriter) - ) - assertThat(lastValue.message).isEqualTo(message) - assertThat(lastValue.type).isEqualTo(TelemetryType.ERROR) - assertThat(lastValue.stack).isEqualTo(stackTrace) - assertThat(lastValue.kind).isEqualTo(kind) - assertThat(lastValue.isMetric).isFalse - assertThat(lastValue.coreConfiguration).isNull() - assertThat(lastValue.additionalProperties).isEqualTo(fakeAdditionalProperties) - } - } - - @Test - fun `M handle error telemetry event W sendErrorTelemetryEvent() {throwable}`( - @StringForgery message: String, - forge: Forge - ) { - // Given - val throwable = forge.aNullable { forge.aThrowable() } - val fakeAdditionalProperties = forge.aNullable { exhaustiveAttributes() } - - // When - testedMonitor.sendErrorTelemetryEvent(message, throwable, fakeAdditionalProperties) - - // Then - argumentCaptor { - verify(mockTelemetryEventHandler).handleEvent( - capture(), - eq(mockWriter) - ) - assertThat(lastValue.message).isEqualTo(message) - assertThat(lastValue.type).isEqualTo(TelemetryType.ERROR) - assertThat(lastValue.stack).isEqualTo(throwable?.loggableStackTrace()) - assertThat(lastValue.kind).isEqualTo(throwable?.javaClass?.canonicalName) - assertThat(lastValue.isMetric).isFalse - assertThat(lastValue.coreConfiguration).isNull() - assertThat(lastValue.additionalProperties).isEqualTo(fakeAdditionalProperties) - } - } - - @Test - fun `M handle configuration telemetry event W sendConfigurationTelemetryEvent()`( - @Forgery fakeConfiguration: TelemetryCoreConfiguration - ) { - // When - testedMonitor.sendConfigurationTelemetryEvent(fakeConfiguration) - - // Then - argumentCaptor { - verify(mockTelemetryEventHandler).handleEvent( - capture(), - eq(mockWriter) - ) - assertThat(lastValue.message).isEmpty() - assertThat(lastValue.type).isEqualTo(TelemetryType.CONFIGURATION) - assertThat(lastValue.stack).isNull() - assertThat(lastValue.kind).isNull() - assertThat(lastValue.isMetric).isFalse - assertThat(lastValue.coreConfiguration).isSameAs(fakeConfiguration) - } - } - - @Test - fun `M handle metric event W sendMetricEvent()`( - @StringForgery message: String, - forge: Forge - ) { - // When - val fakeAdditionalProperties = forge.exhaustiveAttributes() - testedMonitor.sendMetricEvent(message, fakeAdditionalProperties) - - // Then - argumentCaptor { - verify(mockTelemetryEventHandler).handleEvent( - capture(), - eq(mockWriter) - ) - assertThat(lastValue.message).isEqualTo(message) - assertThat(lastValue.type).isEqualTo(TelemetryType.DEBUG) - assertThat(lastValue.stack).isNull() - assertThat(lastValue.kind).isNull() - assertThat(lastValue.coreConfiguration).isNull() - assertThat(lastValue.isMetric).isTrue - assertThat(lastValue.additionalProperties) - .containsExactlyInAnyOrderEntriesOf(fakeAdditionalProperties) - } - } - - @Test - fun `M handle metric event W sendMetricEvent(){additionalProperties is null}`( - @StringForgery message: String - ) { - // When - testedMonitor.sendMetricEvent(message, null) - - // Then - argumentCaptor { - verify(mockTelemetryEventHandler).handleEvent( - capture(), - eq(mockWriter) - ) - assertThat(lastValue.message).isEqualTo(message) - assertThat(lastValue.type).isEqualTo(TelemetryType.DEBUG) - assertThat(lastValue.stack).isNull() - assertThat(lastValue.kind).isNull() - assertThat(lastValue.coreConfiguration).isNull() - assertThat(lastValue.isMetric).isTrue - assertThat(lastValue.additionalProperties).isNull() - } - } - - @Test - fun `M handle interceptor event W notifyInterceptorInstantiated()`() { + fun `M handle telemetry event W sendTelemetryEvent()`(@Forgery fakeTelemetryEvent: TelemetryEvent) { // When - testedMonitor.notifyInterceptorInstantiated() + testedMonitor.sendTelemetryEvent(fakeTelemetryEvent) // Then - argumentCaptor { + argumentCaptor { verify(mockTelemetryEventHandler).handleEvent( capture(), eq(mockWriter) ) - assertThat(lastValue.message).isEmpty() - assertThat(lastValue.type).isEqualTo(TelemetryType.INTERCEPTOR_SETUP) - assertThat(lastValue.stack).isNull() - assertThat(lastValue.kind).isNull() - assertThat(lastValue.isMetric).isFalse - assertThat(lastValue.coreConfiguration).isNull() + assertThat(lastValue.event).isEqualTo(fakeTelemetryEvent) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt index b9a01987f9..d60448bf17 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt @@ -6,6 +6,12 @@ package com.datadog.android.rum.utils.forge +import com.datadog.android.internal.tests.elmyr.TelemetryApiUsageEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.TelemetryEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.TelemetryInternalConfigurationEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.TelemetryLogDebugEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.TelemetryLogErrorEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.TelemetryMetricEventForgeryFactory import com.datadog.android.rum.tests.elmyr.ResourceIdForgeryFactory import com.datadog.android.rum.tests.elmyr.RumScopeKeyForgeryFactory import com.datadog.android.tests.elmyr.useCoreFactories @@ -35,15 +41,22 @@ internal class Configurator : BaseConfigurator() { forge.addFactory(ResourceTimingForgeryFactory()) forge.addFactory(ViewEventForgeryFactory()) forge.addFactory(VitalInfoForgeryFactory()) - forge.addFactory(TelemetryCoreConfigurationForgeryFactory()) forge.addFactory(RumEventMetaForgeryFactory()) forge.addFactory(ViewEventMetaForgeryFactory()) forge.addFactory(RumScopeKeyForgeryFactory()) forge.addFactory(ResourceIdForgeryFactory()) - // Telemetry + // Telemetry schema models forge.addFactory(TelemetryDebugEventForgeryFactory()) forge.addFactory(TelemetryErrorEventForgeryFactory()) forge.addFactory(TelemetryConfigurationEventForgeryFactory()) + + // Telemetry internal models + forge.addFactory(TelemetryEventForgeryFactory()) + forge.addFactory(TelemetryMetricEventForgeryFactory()) + forge.addFactory(TelemetryLogDebugEventForgeryFactory()) + forge.addFactory(TelemetryLogErrorEventForgeryFactory()) + forge.addFactory(TelemetryInternalConfigurationEventForgeryFactory()) + forge.addFactory(TelemetryApiUsageEventForgeryFactory()) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryCoreConfigurationForgeryFactory.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryCoreConfigurationForgeryFactory.kt deleted file mode 100644 index e5694f319c..0000000000 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryCoreConfigurationForgeryFactory.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.rum.utils.forge - -import com.datadog.android.telemetry.internal.TelemetryCoreConfiguration -import fr.xgouchet.elmyr.Forge -import fr.xgouchet.elmyr.ForgeryFactory - -internal class TelemetryCoreConfigurationForgeryFactory : - ForgeryFactory { - override fun getForgery(forge: Forge): TelemetryCoreConfiguration { - return TelemetryCoreConfiguration( - trackErrors = forge.aBool(), - batchSize = forge.aPositiveLong(), - batchUploadFrequency = forge.aPositiveLong(), - useProxy = forge.aBool(), - useLocalEncryption = forge.aBool(), - batchProcessingLevel = forge.aPositiveInt() - ) - } -} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfigurationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfigurationTest.kt deleted file mode 100644 index eaac829b9e..0000000000 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryCoreConfigurationTest.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.telemetry.internal - -import fr.xgouchet.elmyr.annotation.AdvancedForgery -import fr.xgouchet.elmyr.annotation.BoolForgery -import fr.xgouchet.elmyr.annotation.IntForgery -import fr.xgouchet.elmyr.annotation.LongForgery -import fr.xgouchet.elmyr.annotation.MapForgery -import fr.xgouchet.elmyr.annotation.StringForgery -import fr.xgouchet.elmyr.annotation.StringForgeryType -import fr.xgouchet.elmyr.junit5.ForgeExtension -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.extension.Extensions -import org.mockito.kotlin.mock - -@Extensions( - ExtendWith(ForgeExtension::class) -) -internal class TelemetryCoreConfigurationTest { - - @Test - fun `M create TelemetryCoreConfiguration W fromEvent()`( - @BoolForgery trackErrors: Boolean, - @BoolForgery useProxy: Boolean, - @BoolForgery useLocalEncryption: Boolean, - @LongForgery(min = 0L) batchSize: Long, - @LongForgery(min = 0L) batchUploadFrequency: Long, - @IntForgery(min = 0) batchProcessingLevel: Int - ) { - // Given - val event = mapOf( - "type" to "telemetry_configuration", - "track_errors" to trackErrors, - "batch_size" to batchSize, - "batch_upload_frequency" to batchUploadFrequency, - "use_proxy" to useProxy, - "use_local_encryption" to useLocalEncryption, - "batch_processing_level" to batchProcessingLevel - ) - - // When - val coreConfig = TelemetryCoreConfiguration.fromEvent(event, internalLogger = mock()) - - // Then - assertThat(coreConfig).isNotNull - assertThat(coreConfig!!.trackErrors).isEqualTo(trackErrors) - assertThat(coreConfig.batchSize).isEqualTo(batchSize) - assertThat(coreConfig.batchUploadFrequency).isEqualTo(batchUploadFrequency) - assertThat(coreConfig.useProxy).isEqualTo(useProxy) - assertThat(coreConfig.useLocalEncryption).isEqualTo(useLocalEncryption) - assertThat(coreConfig.batchProcessingLevel).isEqualTo(batchProcessingLevel) - } - - @Test - fun `M return null W fromEvent() { malformed message }`( - @MapForgery( - key = AdvancedForgery(string = [StringForgery(StringForgeryType.ALPHABETICAL)]), - value = AdvancedForgery(string = [StringForgery(StringForgeryType.ALPHABETICAL)]) - ) fakeEvent: Map - ) { - // When - val coreConfig = TelemetryCoreConfiguration.fromEvent(fakeEvent, internalLogger = mock()) - - // Then - assertThat(coreConfig).isNull() - } -} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index f3fddae894..769e9207d5 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -15,8 +15,8 @@ import com.datadog.android.api.storage.DataWriter import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore -import com.datadog.android.core.internal.utils.loggableStackTrace import com.datadog.android.core.sampling.Sampler +import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.RumRawEvent @@ -33,8 +33,6 @@ import com.datadog.android.telemetry.assertj.TelemetryErrorEventAssert.Companion import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent -import com.datadog.tools.unit.forge.aThrowable -import com.datadog.tools.unit.forge.exhaustiveAttributes import com.datadog.tools.unit.setStaticValue import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery @@ -197,24 +195,29 @@ internal class TelemetryEventHandlerTest { // region Debug Event @Test - fun `M create debug event W handleEvent(SendTelemetry) { debug event status }`(forge: Forge) { + fun `M create debug event W handleEvent(Log Debug)`(@Forgery fakeLogDebugEvent: TelemetryEvent.Log.Debug) { // Given - val debugRawEvent = forge.createRumRawTelemetryDebugEvent() + val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeLogDebugEvent) // When - testedTelemetryHandler.handleEvent(debugRawEvent, mockWriter) + testedTelemetryHandler.handleEvent(fakeWrappedEvent, mockWriter) // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertDebugEventMatchesRawEvent(lastValue, debugRawEvent, fakeRumContext) + assertDebugEventMatchesInternalEvent( + lastValue, + fakeLogDebugEvent, + fakeRumContext, + fakeWrappedEvent.eventTime.timestamp + ) } } @Test - fun `M create debug event W handleEvent(SendTelemetry) { debug event status, no RUM }`(forge: Forge) { + fun `M create debug event W handleEvent(Log Debug, no RUM)`(@Forgery fakeLogDebugEvent: TelemetryEvent.Log.Debug) { // Given - val debugRawEvent = forge.createRumRawTelemetryDebugEvent() + val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeLogDebugEvent) fakeDatadogContext = fakeDatadogContext.copy( featuresContext = fakeDatadogContext.featuresContext.toMutableMap().apply { remove(Feature.RUM_FEATURE_NAME) @@ -228,12 +231,17 @@ internal class TelemetryEventHandlerTest { ) // When - testedTelemetryHandler.handleEvent(debugRawEvent, mockWriter) + testedTelemetryHandler.handleEvent(fakeWrappedEvent, mockWriter) // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertDebugEventMatchesRawEvent(lastValue, debugRawEvent, noRumContext) + assertDebugEventMatchesInternalEvent( + lastValue, + fakeLogDebugEvent, + noRumContext, + fakeWrappedEvent.eventTime.timestamp + ) } } @@ -242,24 +250,9 @@ internal class TelemetryEventHandlerTest { // region Error Event @Test - fun `M create error event W handleEvent(SendTelemetry) { error event status }`(forge: Forge) { - // Given - val errorRawEvent = forge.createRumRawTelemetryErrorEvent() - - // When - testedTelemetryHandler.handleEvent(errorRawEvent, mockWriter) - - // Then - argumentCaptor { - verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertErrorEventMatchesRawEvent(lastValue, errorRawEvent, fakeRumContext) - } - } - - @Test - fun `M create error event W handleEvent(SendTelemetry) { error event status, no RUM }`(forge: Forge) { + fun `M create error event W handleEvent(Log Error)`(@Forgery fakeLogErrorEvent: TelemetryEvent.Log.Error) { // Given - val errorRawEvent = forge.createRumRawTelemetryErrorEvent() + val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeLogErrorEvent) fakeDatadogContext = fakeDatadogContext.copy( featuresContext = fakeDatadogContext.featuresContext.toMutableMap().apply { remove(Feature.RUM_FEATURE_NAME) @@ -273,12 +266,17 @@ internal class TelemetryEventHandlerTest { ) // When - testedTelemetryHandler.handleEvent(errorRawEvent, mockWriter) + testedTelemetryHandler.handleEvent(fakeWrappedEvent, mockWriter) // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertErrorEventMatchesRawEvent(lastValue, errorRawEvent, noRumContext) + assertErrorEventMatchesInternalEvent( + lastValue, + fakeLogErrorEvent, + noRumContext, + fakeWrappedEvent.eventTime.timestamp + ) } } @@ -287,24 +285,33 @@ internal class TelemetryEventHandlerTest { // region Configuration Event @Test - fun `M create config event W handleEvent(SendTelemetry) { configuration }`(forge: Forge) { + fun `M create config event W handleEvent(Configuration)`( + @Forgery fakeConfigurationEvent: TelemetryEvent.Configuration + ) { // Given - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent() + val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeConfigurationEvent) // When - testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + testedTelemetryHandler.handleEvent(fakeWrappedEvent, mockWriter) // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent, fakeRumContext) + assertConfigEventMatchesInternalEvent( + firstValue, + fakeConfigurationEvent, + fakeRumContext, + fakeWrappedEvent.eventTime.timestamp + ) } } @Test - fun `M create config event W handleEvent(SendTelemetry) { configuration, no RUM }`(forge: Forge) { + fun `M create config event W handleEvent() { Configuration, no RUM)`( + @Forgery fakeConfigurationEvent: TelemetryEvent.Configuration + ) { // Given - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent() + val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeConfigurationEvent) fakeDatadogContext = fakeDatadogContext.copy( featuresContext = fakeDatadogContext.featuresContext.toMutableMap().apply { remove(Feature.RUM_FEATURE_NAME) @@ -318,26 +325,31 @@ internal class TelemetryEventHandlerTest { ) // When - testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) + testedTelemetryHandler.handleEvent(fakeWrappedEvent, mockWriter) // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent, noRumContext) + assertConfigEventMatchesInternalEvent( + firstValue, + fakeConfigurationEvent, + noRumContext, + fakeWrappedEvent.eventTime.timestamp + ) } } @Test - fun `M create config event W handleEvent(SendTelemetry) { with RUM config }`( + fun `M create config event W handleEvent() {Configuration, with RUM config }`( @Forgery fakeRumConfiguration: RumFeature.Configuration, - forge: Forge + @Forgery fakeConfigurationEvent: TelemetryEvent.Configuration ) { // Given val mockRumFeature = mock() whenever(mockRumFeature.configuration) doReturn fakeRumConfiguration whenever(mockRumFeatureScope.unwrap()) doReturn mockRumFeature - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent() + val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfigurationEvent) val expectedViewTrackingStrategy = when (fakeRumConfiguration.viewTrackingStrategy) { is ActivityViewTrackingStrategy -> VTS.ACTIVITYVIEWTRACKINGSTRATEGY @@ -353,7 +365,12 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent, fakeRumContext) + assertConfigEventMatchesInternalEvent( + firstValue, + fakeConfigurationEvent, + fakeRumContext, + configRawEvent.eventTime.timestamp + ) assertThat(firstValue) .hasSessionSampleRate(fakeRumConfiguration.sampleRate.toLong()) .hasTelemetrySampleRate(fakeRumConfiguration.telemetrySampleRate.toLong()) @@ -367,41 +384,16 @@ internal class TelemetryEventHandlerTest { } } - @Test - fun `M create config event W handleEvent(SendTelemetry) { with Core config }`( - @Forgery fakeCoreConfiguration: TelemetryCoreConfiguration, - forge: Forge - ) { - // Given - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(fakeCoreConfiguration) - - // When - testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) - - // Then - argumentCaptor { - verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent, fakeRumContext) - assertThat(firstValue) - .hasUseProxy(fakeCoreConfiguration.useProxy) - .hasUseLocalEncryption(fakeCoreConfiguration.useLocalEncryption) - .hasTrackErrors(fakeCoreConfiguration.trackErrors) - .hasBatchSize(fakeCoreConfiguration.batchSize) - .hasBatchUploadFrequency(fakeCoreConfiguration.batchUploadFrequency) - .hasBatchProcessingLevel(fakeCoreConfiguration.batchProcessingLevel) - } - } - @ParameterizedTest @MethodSource("tracingConfigurationParameters") - fun `M create config event W handleEvent(SendTelemetry) { tracing configuration with tracing settings }`( + fun `M create config event W handleEvent() { tracing configuration with tracing settings }`( useTracer: Boolean, tracerApi: TelemetryEventHandler.TracerApi?, tracerApiVersion: String?, - @Forgery fakeConfiguration: TelemetryCoreConfiguration + @Forgery fakeConfiguration: TelemetryEvent.Configuration ) { // Given - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(fakeConfiguration) + val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) if (useTracer) { whenever(mockSdkCore.getFeature(Feature.TRACING_FEATURE_NAME)) doReturn mock() if (tracerApi == TelemetryEventHandler.TracerApi.OpenTracing) { @@ -421,7 +413,12 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent, fakeRumContext) + assertConfigEventMatchesInternalEvent( + firstValue, + fakeConfiguration, + fakeRumContext, + configRawEvent.eventTime.timestamp + ) assertThat(firstValue) .hasUseTracing(useTracer) .hasTracerApi(tracerApi?.name) @@ -430,25 +427,18 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M create config event W handleEvent(SendTelemetry) { configuration with interceptor }`( - @Forgery fakeConfiguration: TelemetryCoreConfiguration, + fun `M create config event W handleEvent() { configuration with interceptor }`( + @Forgery fakeConfiguration: TelemetryEvent.Configuration, forge: Forge ) { // Given val trackNetworkRequests = forge.aBool() - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent(fakeConfiguration) + val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) // When if (trackNetworkRequests) { testedTelemetryHandler.handleEvent( - RumRawEvent.SendTelemetry( - TelemetryType.INTERCEPTOR_SETUP, - "", - null, - null, - coreConfiguration = null, - additionalProperties = null - ), + RumRawEvent.TelemetryEventWrapper(TelemetryEvent.InterceptorInstantiated), mockWriter ) } @@ -457,18 +447,23 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent, fakeRumContext) + assertConfigEventMatchesInternalEvent( + firstValue, + fakeConfiguration, + fakeRumContext, + configRawEvent.eventTime.timestamp + ) assertThat(firstValue) .hasTrackNetworkRequests(trackNetworkRequests) } } @Test - fun `M create config event W handleEvent(SendTelemetry) { configuration, no SessionReplay }`( - forge: Forge + fun `M create config event W handleEvent() { configuration, no SessionReplay }`( + @Forgery fakeConfiguration: TelemetryEvent.Configuration ) { // Given - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent() + val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) // When testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) @@ -476,7 +471,7 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent) + assertConfigEventMatchesInternalEvent(firstValue, fakeConfiguration, configRawEvent.eventTime.timestamp) assertThat(firstValue).hasSessionReplaySampleRate(null) assertThat(firstValue).hasStartRecordingImmediately(null) assertThat(firstValue).hasSessionReplayImagePrivacy(null) @@ -486,7 +481,8 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M create config event W handleEvent(SendTelemetry) { configuration, with SessionReplay }`( + fun `M create config event W handleEvent() { configuration, with SessionReplay }`( + @Forgery fakeConfiguration: TelemetryEvent.Configuration, forge: Forge ) { // Given @@ -505,7 +501,7 @@ internal class TelemetryEventHandlerTest { ) whenever(mockSdkCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn fakeSessionReplayContext - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent() + val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) // When testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) @@ -513,7 +509,7 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent) + assertConfigEventMatchesInternalEvent(firstValue, fakeConfiguration, configRawEvent.eventTime.timestamp) assertThat(firstValue).hasSessionReplaySampleRate(fakeSampleRate) assertThat(firstValue).hasStartRecordingImmediately(fakeSessionReplayIsStartImmediately) assertThat(firstValue).hasSessionReplayImagePrivacy(fakeSessionReplayImagePrivacy) @@ -524,6 +520,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent(SendTelemetry) { with SessionReplay, bad format }`( + @Forgery fakeConfiguration: TelemetryEvent.Configuration, forge: Forge ) { // Given @@ -536,7 +533,7 @@ internal class TelemetryEventHandlerTest { ) whenever(mockSdkCore.getFeatureContext(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn fakeSessionReplayContext - val configRawEvent = forge.createRumRawTelemetryConfigurationEvent() + val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) // When testedTelemetryHandler.handleEvent(configRawEvent, mockWriter) @@ -544,7 +541,7 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - assertConfigEventMatchesRawEvent(firstValue, configRawEvent) + assertConfigEventMatchesInternalEvent(firstValue, fakeConfiguration, configRawEvent.eventTime.timestamp) assertThat(firstValue).hasSessionReplaySampleRate(null) assertThat(firstValue).hasStartRecordingImmediately(null) assertThat(firstValue).hasSessionReplayImagePrivacy(null) @@ -558,9 +555,11 @@ internal class TelemetryEventHandlerTest { // region Sampling @Test - fun `M not write event W handleEvent(SendTelemetry) { event is not sampled }`(forge: Forge) { + fun `M not write event W handleEvent() { event is not sampled }`( + @Forgery fakeTelemetryEvent: TelemetryEvent + ) { // Given - val rawEvent = forge.createRumRawTelemetryEvent() + val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeTelemetryEvent) whenever(mockSampler.sample()) doReturn false // When @@ -571,14 +570,15 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M write debug&error event W handleEvent(SendTelemetry) { configuration sampler returns false }`( + fun `M write debug&error event W handleEvent() { log events, configuration sampler returns false }`( forge: Forge ) { // Given - val rawEvent = forge.anElementFrom( - forge.createRumRawTelemetryDebugEvent(), - forge.createRumRawTelemetryErrorEvent() + val logeEvent = forge.anElementFrom( + forge.getForgery(), + forge.getForgery() ) + val rawEvent = RumRawEvent.TelemetryEventWrapper(logeEvent) whenever(mockSampler.sample()) doReturn true whenever(mockConfigurationSampler.sample()) doReturn false @@ -588,7 +588,7 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - if (rawEvent.type == TelemetryType.DEBUG) { + if (logeEvent is TelemetryEvent.Log.Debug) { assertThat(lastValue).isInstanceOf(TelemetryDebugEvent::class.java) } else { assertThat(lastValue).isInstanceOf(TelemetryErrorEvent::class.java) @@ -597,11 +597,11 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M not write configuration event W handleEvent(SendTelemetry) { event is not sampled }`( - forge: Forge + fun `M not write configuration event W handleEvent() { event is not sampled }`( + @Forgery fakeConfiguration: TelemetryEvent.Configuration ) { // Given - val rawEvent = forge.createRumRawTelemetryConfigurationEvent() + val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) whenever(mockSampler.sample()) doReturn true whenever(mockConfigurationSampler.sample()) doReturn false @@ -613,11 +613,17 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M not write event W handleEvent(SendTelemetry){seen in the session, not metric}`( + fun `M not write event W handleEvent(){ seen in the session, not metric }`( forge: Forge ) { // Given - val rawEvent = forge.createRumRawTelemetryEvent().copy(isMetric = false) + val telemetryEvent = forge.anElementFrom( + forge.getForgery(), + forge.getForgery(), + forge.getForgery(), + TelemetryEvent.InterceptorInstantiated + ) + val rawEvent = RumRawEvent.TelemetryEventWrapper(telemetryEvent) val anotherEvent = rawEvent.copy() // When @@ -630,11 +636,7 @@ internal class TelemetryEventHandlerTest { InternalLogger.Target.MAINTAINER, TelemetryEventHandler.ALREADY_SEEN_EVENT_MESSAGE.format( Locale.US, - TelemetryEventId( - rawEvent.type, - rawEvent.message, - rawEvent.kind - ) + telemetryEvent.identity ) ) @@ -642,15 +644,34 @@ internal class TelemetryEventHandlerTest { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) when (val capturedValue = lastValue) { is TelemetryDebugEvent -> { - assertDebugEventMatchesRawEvent(capturedValue, rawEvent, fakeRumContext) + assertDebugEventMatchesInternalEvent( + capturedValue, + telemetryEvent as TelemetryEvent.Log.Debug, + fakeRumContext, + rawEvent.eventTime.timestamp + ) } is TelemetryErrorEvent -> { - assertErrorEventMatchesRawEvent(capturedValue, rawEvent, fakeRumContext) + assertErrorEventMatchesInternalEvent( + capturedValue, + telemetryEvent as TelemetryEvent.Log.Error, + fakeRumContext, + rawEvent.eventTime.timestamp + ) } is TelemetryConfigurationEvent -> { - assertConfigEventMatchesRawEvent(capturedValue, rawEvent, fakeRumContext) + assertConfigEventMatchesInternalEvent( + capturedValue, + telemetryEvent as TelemetryEvent.Configuration, + fakeRumContext, + rawEvent.eventTime.timestamp + ) + } + + is TelemetryEvent.InterceptorInstantiated -> { + assertThat(capturedValue).isEqualTo(TelemetryEvent.InterceptorInstantiated) } else -> throw IllegalArgumentException( @@ -662,12 +683,12 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M write event W handleEvent(SendTelemetry){seen in the session, is metric}`( - forge: Forge + fun `M write event W handleEvent(){ seen in the session, is metric }`( + @Forgery fakeMetricEvent: TelemetryEvent.Metric ) { // Given - val rawEvent = forge.createRumRawTelemetryEvent().copy(isMetric = true) - val events = listOf(rawEvent, rawEvent.copy()) + val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeMetricEvent) + val events = listOf(rawEvent, RumRawEvent.TelemetryEventWrapper(fakeMetricEvent)) // When testedTelemetryHandler.handleEvent(events[0], mockWriter) @@ -676,57 +697,30 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter, times(2)).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - allValues.withIndex().forEach { - when (val capturedValue = it.value) { - is TelemetryDebugEvent -> { - assertDebugEventMatchesRawEvent( - capturedValue, - events[it.index], - fakeRumContext - ) - } - - is TelemetryErrorEvent -> { - assertErrorEventMatchesRawEvent( - capturedValue, - events[it.index], - fakeRumContext - ) - } - - is TelemetryConfigurationEvent -> { - assertConfigEventMatchesRawEvent( - capturedValue, - events[it.index], - fakeRumContext - ) - } - - else -> throw IllegalArgumentException( - "Unexpected type=${lastValue::class.jvmName} of the captured value." - ) - } - } + assertDebugEventMatchesMetricInternalEvent( + firstValue as TelemetryDebugEvent, + fakeMetricEvent, + fakeRumContext, + rawEvent.eventTime.timestamp + ) + assertDebugEventMatchesMetricInternalEvent( + secondValue as TelemetryDebugEvent, + fakeMetricEvent, + fakeRumContext, + rawEvent.eventTime.timestamp + ) } } @Test - fun `M not write events over the limit W handleEvent(SendTelemetry)`() { - // Given - val event = RumRawEvent.SendTelemetry( - TelemetryType.DEBUG, - "Metric event", - null, - null, - coreConfiguration = null, - additionalProperties = null, - isMetric = true - ) - val events = (0..MAX_EVENTS_PER_SESSION_TEST).map { event } + fun `M not write events over the limit W handleEvent() { metric event }`( + forge: Forge + ) { + val events = (0..MAX_EVENTS_PER_SESSION_TEST).map { forge.getForgery() } // When events.forEach { - testedTelemetryHandler.handleEvent(it, mockWriter) + testedTelemetryHandler.handleEvent(RumRawEvent.TelemetryEventWrapper(it), mockWriter) } // Then @@ -738,19 +732,15 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M continue writing events after new session W handleEvent(SendTelemetry)`(forge: Forge) { + fun `M continue writing events after new session W handleEvent() { metric event }`(forge: Forge) { // Given - val event = RumRawEvent.SendTelemetry( - TelemetryType.DEBUG, - "Metric event", - null, - null, - coreConfiguration = null, - additionalProperties = null, - isMetric = true // important because non-metric events can only be seen once - ) - val eventsInOldSession = (0..MAX_EVENTS_PER_SESSION_TEST / 2).map { event } - val eventsInNewSession = (0..MAX_EVENTS_PER_SESSION_TEST / 2).map { event } + // important because non-metric events can only be seen once + val eventsInOldSession = (0..MAX_EVENTS_PER_SESSION_TEST / 2).map { + forge.getForgery() + }.map { RumRawEvent.TelemetryEventWrapper(it) } + val eventsInNewSession = (0..MAX_EVENTS_PER_SESSION_TEST / 2).map { + forge.getForgery() + }.map { RumRawEvent.TelemetryEventWrapper(it) } eventsInOldSession.forEach { testedTelemetryHandler.handleEvent(it, mockWriter) @@ -768,7 +758,7 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M count the limit only after the sampling W handleEvent(SendTelemetry)`(forge: Forge) { + fun `M count the limit only after the sampling W handleEvent()`(forge: Forge) { // Given // sample out 50% whenever(mockSampler.sample()) doAnswer object : Answer { @@ -781,10 +771,10 @@ internal class TelemetryEventHandlerTest { val events = forge.aList( size = MAX_EVENTS_PER_SESSION_TEST * 10 - ) { createRumRawTelemetryEvent() } + ) { forge.getForgery() } // remove unwanted identity collisions .groupBy { it.identity } - .map { it.value.first() } + .map { RumRawEvent.TelemetryEventWrapper(it.value.first()) } .take(MAX_EVENTS_PER_SESSION_TEST * 2) assumeTrue(events.size == MAX_EVENTS_PER_SESSION_TEST * 2) @@ -801,26 +791,52 @@ internal class TelemetryEventHandlerTest { verifyNoInteractions(mockInternalLogger) } - // endregion +// endregion + +// region Assertions - // region Assertions + private fun assertDebugEventMatchesInternalEvent( + actual: TelemetryDebugEvent, + internalDebugEvent: TelemetryEvent.Log.Debug, + rumContext: RumContext, + time: Long + ) { + assertThat(actual) + .hasDate(time + fakeServerOffset) + .hasSource(TelemetryDebugEvent.Source.ANDROID) + .hasMessage(internalDebugEvent.message) + .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) + .hasVersion(fakeDatadogContext.sdkVersion) + .hasApplicationId(rumContext.applicationId) + .hasSessionId(rumContext.sessionId) + .hasViewId(rumContext.viewId) + .hasActionId(rumContext.actionId) + .hasAdditionalProperties(internalDebugEvent.additionalProperties ?: emptyMap()) + .hasDeviceArchitecture(fakeDeviceArchitecture) + .hasDeviceBrand(fakeDeviceBrand) + .hasDeviceModel(fakeDeviceModel) + .hasOsBuild(fakeOsBuildId) + .hasOsName(fakeOsName) + .hasOsVersion(fakeOsVersion) + } - private fun assertDebugEventMatchesRawEvent( + private fun assertDebugEventMatchesMetricInternalEvent( actual: TelemetryDebugEvent, - rawEvent: RumRawEvent.SendTelemetry, - rumContext: RumContext + internalMetricEvent: TelemetryEvent.Metric, + rumContext: RumContext, + time: Long ) { assertThat(actual) - .hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) + .hasDate(time + fakeServerOffset) .hasSource(TelemetryDebugEvent.Source.ANDROID) - .hasMessage(rawEvent.message) + .hasMessage(internalMetricEvent.message) .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) .hasVersion(fakeDatadogContext.sdkVersion) .hasApplicationId(rumContext.applicationId) .hasSessionId(rumContext.sessionId) .hasViewId(rumContext.viewId) .hasActionId(rumContext.actionId) - .hasAdditionalProperties(rawEvent.additionalProperties ?: emptyMap()) + .hasAdditionalProperties(internalMetricEvent.additionalProperties ?: emptyMap()) .hasDeviceArchitecture(fakeDeviceArchitecture) .hasDeviceBrand(fakeDeviceBrand) .hasDeviceModel(fakeDeviceModel) @@ -829,39 +845,41 @@ internal class TelemetryEventHandlerTest { .hasOsVersion(fakeOsVersion) } - private fun assertErrorEventMatchesRawEvent( + private fun assertErrorEventMatchesInternalEvent( actual: TelemetryErrorEvent, - rawEvent: RumRawEvent.SendTelemetry, - rumContext: RumContext + internalErrorEvent: TelemetryEvent.Log.Error, + rumContext: RumContext, + time: Long ) { assertThat(actual) - .hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) + .hasDate(time + fakeServerOffset) .hasSource(TelemetryErrorEvent.Source.ANDROID) - .hasMessage(rawEvent.message) + .hasMessage(internalErrorEvent.message) .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) .hasVersion(fakeDatadogContext.sdkVersion) .hasApplicationId(rumContext.applicationId) .hasSessionId(rumContext.sessionId) .hasViewId(rumContext.viewId) .hasActionId(rumContext.actionId) - .hasErrorStack(rawEvent.stack) - .hasErrorKind(rawEvent.kind) + .hasErrorStack(internalErrorEvent.stacktrace) + .hasErrorKind(internalErrorEvent.kind) .hasDeviceArchitecture(fakeDeviceArchitecture) .hasDeviceBrand(fakeDeviceBrand) .hasDeviceModel(fakeDeviceModel) .hasOsBuild(fakeOsBuildId) .hasOsName(fakeOsName) .hasOsVersion(fakeOsVersion) - .hasAdditionalProperties(rawEvent.additionalProperties ?: emptyMap()) + .hasAdditionalProperties(internalErrorEvent.additionalProperties ?: emptyMap()) } - private fun assertConfigEventMatchesRawEvent( + private fun assertConfigEventMatchesInternalEvent( actual: TelemetryConfigurationEvent, - rawEvent: RumRawEvent.SendTelemetry, - rumContext: RumContext + internalConfigurationEvent: TelemetryEvent.Configuration, + rumContext: RumContext, + time: Long ) { assertThat(actual) - .hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) + .hasDate(time + fakeServerOffset) .hasSource(TelemetryConfigurationEvent.Source.ANDROID) .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) .hasVersion(fakeDatadogContext.sdkVersion) @@ -869,71 +887,33 @@ internal class TelemetryEventHandlerTest { .hasSessionId(rumContext.sessionId) .hasViewId(rumContext.viewId) .hasActionId(rumContext.actionId) + .hasBatchSize(internalConfigurationEvent.batchSize) + .hasBatchUploadFrequency(internalConfigurationEvent.batchUploadFrequency) + .hasBatchProcessingLevel(internalConfigurationEvent.batchProcessingLevel) + .hasTrackErrors(internalConfigurationEvent.trackErrors) + .hasUseProxy(internalConfigurationEvent.useProxy) + .hasUseLocalEncryption(internalConfigurationEvent.useLocalEncryption) } - private fun assertConfigEventMatchesRawEvent( + private fun assertConfigEventMatchesInternalEvent( actual: TelemetryConfigurationEvent, - rawEvent: RumRawEvent.SendTelemetry + internalConfigurationEvent: TelemetryEvent.Configuration, + time: Long ) { assertThat(actual) - .hasDate(rawEvent.eventTime.timestamp + fakeServerOffset) + .hasDate(time + fakeServerOffset) .hasSource(TelemetryConfigurationEvent.Source.ANDROID) .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) .hasVersion(fakeDatadogContext.sdkVersion) + .hasBatchSize(internalConfigurationEvent.batchSize) + .hasBatchUploadFrequency(internalConfigurationEvent.batchUploadFrequency) + .hasBatchProcessingLevel(internalConfigurationEvent.batchProcessingLevel) + .hasTrackErrors(internalConfigurationEvent.trackErrors) + .hasUseProxy(internalConfigurationEvent.useProxy) + .hasUseLocalEncryption(internalConfigurationEvent.useLocalEncryption) } - // endregion - - // region Forgeries - - private fun Forge.createRumRawTelemetryEvent(): RumRawEvent.SendTelemetry { - return anElementFrom( - createRumRawTelemetryDebugEvent(), - createRumRawTelemetryErrorEvent(), - createRumRawTelemetryConfigurationEvent() - ) - } - - private fun Forge.createRumRawTelemetryDebugEvent(): RumRawEvent.SendTelemetry { - return RumRawEvent.SendTelemetry( - TelemetryType.DEBUG, - aString(), - null, - null, - coreConfiguration = null, - additionalProperties = aNullable { exhaustiveAttributes() }, - isMetric = aBool() - ) - } - - private fun Forge.createRumRawTelemetryErrorEvent(): RumRawEvent.SendTelemetry { - val throwable = aNullable { aThrowable() } - return RumRawEvent.SendTelemetry( - TelemetryType.ERROR, - aString(), - throwable?.loggableStackTrace(), - throwable?.javaClass?.canonicalName, - coreConfiguration = null, - additionalProperties = aNullable { exhaustiveAttributes() }, - isMetric = aBool() - ) - } - - private fun Forge.createRumRawTelemetryConfigurationEvent( - configuration: TelemetryCoreConfiguration? = null - ): RumRawEvent.SendTelemetry { - return RumRawEvent.SendTelemetry( - TelemetryType.CONFIGURATION, - "", - null, - null, - coreConfiguration = (configuration ?: getForgery()), - additionalProperties = null, - isMetric = aBool() - ) - } - - // endregion +// endregion companion object { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryTest.kt deleted file mode 100644 index 783505680d..0000000000 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.telemetry.internal - -import com.datadog.android.rum.internal.monitor.AdvancedRumMonitor -import com.datadog.android.rum.utils.config.GlobalRumMonitorTestConfiguration -import com.datadog.android.rum.utils.forge.Configurator -import com.datadog.tools.unit.annotations.TestConfigurationsProvider -import com.datadog.tools.unit.extensions.TestConfigurationExtension -import com.datadog.tools.unit.extensions.config.TestConfiguration -import com.datadog.tools.unit.forge.aThrowable -import com.datadog.tools.unit.forge.exhaustiveAttributes -import fr.xgouchet.elmyr.Forge -import fr.xgouchet.elmyr.annotation.StringForgery -import fr.xgouchet.elmyr.junit5.ForgeConfiguration -import fr.xgouchet.elmyr.junit5.ForgeExtension -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.extension.Extensions -import org.mockito.junit.jupiter.MockitoExtension -import org.mockito.junit.jupiter.MockitoSettings -import org.mockito.kotlin.verify -import org.mockito.quality.Strictness - -@Extensions( - ExtendWith(MockitoExtension::class), - ExtendWith(ForgeExtension::class), - ExtendWith(TestConfigurationExtension::class) -) -@MockitoSettings(strictness = Strictness.LENIENT) -@ForgeConfiguration(Configurator::class) -internal class TelemetryTest { - - private lateinit var testedTelemetry: Telemetry - - @BeforeEach - fun `set up`() { - testedTelemetry = Telemetry(rumMonitor.mockSdkCore) - } - - @Test - fun `M report error event W error()`( - @StringForgery message: String, - forge: Forge - ) { - // Given - val throwable = forge.aNullable { forge.aThrowable() } - val fakeAdditionalProperties = forge.aNullable { exhaustiveAttributes() } - - // When - testedTelemetry.error(message, throwable, fakeAdditionalProperties) - - // Then - verify(rumMonitor.mockInstance as AdvancedRumMonitor) - .sendErrorTelemetryEvent(message, throwable, fakeAdditionalProperties) - } - - @Test - fun `M report debug event W debug()`( - @StringForgery message: String, - forge: Forge - ) { - // Given - val fakeAdditionalProperties = forge.aNullable { exhaustiveAttributes() } - - // When - testedTelemetry.debug(message, fakeAdditionalProperties) - - // Then - verify(rumMonitor.mockInstance as AdvancedRumMonitor) - .sendDebugTelemetryEvent(message, fakeAdditionalProperties) - } - - @Test - fun `M report metric event W metric()`( - @StringForgery message: String, - forge: Forge - ) { - // Given - val fakeAdditionalProperties = forge.aNullable { exhaustiveAttributes() } - - // When - testedTelemetry.metric(message, fakeAdditionalProperties) - - // Then - verify(rumMonitor.mockInstance as AdvancedRumMonitor) - .sendMetricEvent(message, fakeAdditionalProperties) - } - - companion object { - val rumMonitor = GlobalRumMonitorTestConfiguration() - - @TestConfigurationsProvider - @JvmStatic - fun getTestConfigurations(): List { - return listOf(rumMonitor) - } - } -} diff --git a/reliability/stub-core/build.gradle.kts b/reliability/stub-core/build.gradle.kts index 335c0e69ce..7f63aa6fa6 100644 --- a/reliability/stub-core/build.gradle.kts +++ b/reliability/stub-core/build.gradle.kts @@ -27,6 +27,7 @@ android { } dependencies { + implementation(project(":dd-sdk-android-internal")) implementation(project(":dd-sdk-android-core")) implementation(libs.kotlin) diff --git a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt index c7b8eb018c..a0a59c872c 100644 --- a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt +++ b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt @@ -9,6 +9,7 @@ package com.datadog.android.core.stub import com.datadog.android.api.InternalLogger import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType +import com.datadog.android.internal.telemetry.TelemetryEvent @Suppress("UnsafeThirdPartyFunctionCall") internal class StubInternalLogger : InternalLogger { @@ -63,6 +64,14 @@ internal class StubInternalLogger : InternalLogger { return null } + override fun logApiUsage( + apiUsageEvent: TelemetryEvent.ApiUsage, + samplingRate: Float + ) { + println("AU [T]: ${apiUsageEvent::class.simpleName} | $samplingRate%") + apiUsageEvent.additionalProperties.log() + } + private fun Map.log() { forEach { println(" ${it.key}: ${it.value}") From 40283db0d359eef4abba1f3cb1bb790128b3e687 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Fri, 13 Sep 2024 11:34:24 +0300 Subject: [PATCH 026/111] Rename classes --- dd-sdk-android-core/api/apiSurface | 2 +- .../api/dd-sdk-android-core.api | 2 +- .../com/datadog/android/_InternalProxy.kt | 8 +- .../com/datadog/android/api/InternalLogger.kt | 4 +- .../android/core/internal/DatadogCore.kt | 6 +- .../core/internal/logger/SdkInternalLogger.kt | 10 +-- .../com/datadog/android/InternalProxyTest.kt | 8 +- .../core/DatadogCoreInitializationTest.kt | 33 +++++--- .../datadog/android/core/DatadogCoreTest.kt | 49 ----------- .../internal/logger/SdkInternalLoggerTest.kt | 36 ++++---- .../android/utils/forge/Configurator.kt | 4 +- dd-sdk-android-internal/api/apiSurface | 12 +-- .../api/dd-sdk-android-internal.api | 28 +++---- ...etryEvent.kt => InternalTelemetryEvent.kt} | 13 +-- ...nternalTelemetryApiUsageForgeryFactory.kt} | 8 +- ...alTelemetryConfigurationForgeryFactory.kt} | 8 +- ...nternalTelemetryDebugLogForgeryFactory.kt} | 8 +- ...nternalTelemetryErrorLogForgeryFactory.kt} | 8 +- .../InternalTelemetryEventForgeryFactory.kt | 28 +++++++ ... InternalTelemetryMetricForgeryFactory.kt} | 8 +- .../elmyr/TelemetryEventForgeryFactory.kt | 28 ------- .../android/rum/internal/RumFeature.kt | 4 +- .../rum/internal/domain/scope/RumRawEvent.kt | 4 +- .../internal/monitor/AdvancedRumMonitor.kt | 4 +- .../rum/internal/monitor/DatadogRumMonitor.kt | 6 +- .../internal/TelemetryEventHandler.kt | 26 +++--- .../telemetry/internal/TelemetryEventId.kt | 22 ++--- .../android/rum/internal/RumFeatureTest.kt | 8 +- .../rum/internal/metric/FakeInternalLogger.kt | 4 +- .../internal/monitor/DatadogRumMonitorTest.kt | 8 +- .../android/rum/utils/forge/Configurator.kt | 24 +++--- .../internal/TelemetryEventHandlerTest.kt | 84 ++++++++++--------- .../android/core/stub/StubInternalLogger.kt | 4 +- 33 files changed, 235 insertions(+), 274 deletions(-) rename dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/{TelemetryEvent.kt => InternalTelemetryEvent.kt} (84%) rename dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/{TelemetryApiUsageEventForgeryFactory.kt => InternalTelemetryApiUsageForgeryFactory.kt} (66%) rename dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/{TelemetryInternalConfigurationEventForgeryFactory.kt => InternalTelemetryConfigurationForgeryFactory.kt} (67%) rename dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/{TelemetryMetricEventForgeryFactory.kt => InternalTelemetryDebugLogForgeryFactory.kt} (63%) rename dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/{TelemetryLogErrorEventForgeryFactory.kt => InternalTelemetryErrorLogForgeryFactory.kt} (71%) create mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryEventForgeryFactory.kt rename dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/{TelemetryLogDebugEventForgeryFactory.kt => InternalTelemetryMetricForgeryFactory.kt} (64%) delete mode 100644 dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt diff --git a/dd-sdk-android-core/api/apiSurface b/dd-sdk-android-core/api/apiSurface index 673a0e9ee6..669c3cd39a 100644 --- a/dd-sdk-android-core/api/apiSurface +++ b/dd-sdk-android-core/api/apiSurface @@ -44,7 +44,7 @@ interface com.datadog.android.api.InternalLogger fun log(Level, List, () -> String, Throwable? = null, Boolean = false, Map? = null) fun logMetric(() -> String, Map, Float) fun startPerformanceMeasure(String, com.datadog.android.core.metrics.TelemetryMetricType, Float, String): com.datadog.android.core.metrics.PerformanceMetric? - fun logApiUsage(com.datadog.android.internal.telemetry.TelemetryEvent.ApiUsage, Float) + fun logApiUsage(com.datadog.android.internal.telemetry.InternalTelemetryEvent.ApiUsage, Float) companion object val UNBOUND: InternalLogger interface com.datadog.android.api.SdkCore diff --git a/dd-sdk-android-core/api/dd-sdk-android-core.api b/dd-sdk-android-core/api/dd-sdk-android-core.api index f32c80ce3e..1795e51ba9 100644 --- a/dd-sdk-android-core/api/dd-sdk-android-core.api +++ b/dd-sdk-android-core/api/dd-sdk-android-core.api @@ -77,7 +77,7 @@ public abstract interface class com/datadog/android/api/InternalLogger { public static final field Companion Lcom/datadog/android/api/InternalLogger$Companion; public abstract fun log (Lcom/datadog/android/api/InternalLogger$Level;Lcom/datadog/android/api/InternalLogger$Target;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;)V public abstract fun log (Lcom/datadog/android/api/InternalLogger$Level;Ljava/util/List;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;)V - public abstract fun logApiUsage (Lcom/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage;F)V + public abstract fun logApiUsage (Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage;F)V public abstract fun logMetric (Lkotlin/jvm/functions/Function0;Ljava/util/Map;F)V public abstract fun startPerformanceMeasure (Ljava/lang/String;Lcom/datadog/android/core/metrics/TelemetryMetricType;FLjava/lang/String;)Lcom/datadog/android/core/metrics/PerformanceMetric; } diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt index d64c015f1a..4a043dcbdd 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt @@ -12,7 +12,7 @@ import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.internal.DatadogCore -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.lint.InternalApi /** @@ -46,7 +46,7 @@ class _InternalProxy internal constructor( } fun debug(message: String) { - val telemetryEvent = TelemetryEvent.Log.Debug( + val telemetryEvent = InternalTelemetryEvent.Log.Debug( message = message, additionalProperties = null ) @@ -54,7 +54,7 @@ class _InternalProxy internal constructor( } fun error(message: String, throwable: Throwable? = null) { - val telemetryEvent = TelemetryEvent.Log.Error( + val telemetryEvent = InternalTelemetryEvent.Log.Error( message = message, error = throwable ) @@ -62,7 +62,7 @@ class _InternalProxy internal constructor( } fun error(message: String, stack: String?, kind: String?) { - val telemetryEvent = TelemetryEvent.Log.Error( + val telemetryEvent = InternalTelemetryEvent.Log.Error( message = message, stacktrace = stack, kind = kind diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt index e31e21ccae..f49d4c8229 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt @@ -9,7 +9,7 @@ package com.datadog.android.api import com.datadog.android.core.internal.logger.SdkInternalLogger import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.lint.InternalApi import com.datadog.tools.annotation.NoOpImplementation @@ -141,7 +141,7 @@ interface InternalLogger { */ @InternalApi fun logApiUsage( - apiUsageEvent: TelemetryEvent.ApiUsage, + apiUsageEvent: InternalTelemetryEvent.ApiUsage, samplingRate: Float ) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt index 87a6aa2df7..745fec66a0 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt @@ -38,7 +38,7 @@ import com.datadog.android.core.internal.utils.scheduleSafe import com.datadog.android.core.internal.utils.submitSafe import com.datadog.android.core.thread.FlushableExecutorService import com.datadog.android.error.internal.CrashReportsFeature -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.ndk.internal.NdkCrashHandler import com.datadog.android.privacy.TrackingConsent import com.google.gson.JsonObject @@ -500,10 +500,10 @@ internal class DatadogCore( } @Suppress("FunctionMaxLength") - internal fun sendCoreConfigurationTelemetryEvent(configuration: Configuration) { + private fun sendCoreConfigurationTelemetryEvent(configuration: Configuration) { val runnable = Runnable { val rumFeature = getFeature(Feature.RUM_FEATURE_NAME) ?: return@Runnable - val event = TelemetryEvent.Configuration( + val event = InternalTelemetryEvent.Configuration( trackErrors = configuration.crashReportsEnabled, batchSize = configuration.coreConfig.batchSize.windowDurationMs, useProxy = configuration.coreConfig.proxy != null, diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt index 1b13669b9c..2e17f38223 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt @@ -16,7 +16,7 @@ import com.datadog.android.core.internal.metrics.MethodCalledTelemetry import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType import com.datadog.android.core.sampling.RateBasedSampler -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent internal class SdkInternalLogger( private val sdkCore: FeatureSdkCore?, @@ -100,7 +100,7 @@ internal class SdkInternalLogger( ) { if (!RateBasedSampler(samplingRate).sample()) return val rumFeature = sdkCore?.getFeature(Feature.RUM_FEATURE_NAME) ?: return - val metricEvent = TelemetryEvent.Metric( + val metricEvent = InternalTelemetryEvent.Metric( message = messageBuilder(), additionalProperties = additionalProperties ) @@ -127,7 +127,7 @@ internal class SdkInternalLogger( } override fun logApiUsage( - apiUsageEvent: TelemetryEvent.ApiUsage, + apiUsageEvent: InternalTelemetryEvent.ApiUsage, samplingRate: Float ) { if (!RateBasedSampler(samplingRate).sample()) return @@ -223,13 +223,13 @@ internal class SdkInternalLogger( level == InternalLogger.Level.WARN || error != null ) { - TelemetryEvent.Log.Error( + InternalTelemetryEvent.Log.Error( message = message, additionalProperties = additionalProperties, error = error ) } else { - TelemetryEvent.Log.Debug( + InternalTelemetryEvent.Log.Debug( message = message, additionalProperties = additionalProperties ) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt index ec26ee4751..37fdd2281f 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt @@ -11,7 +11,7 @@ import com.datadog.android.api.feature.FeatureScope import com.datadog.android.core.internal.CoreFeature import com.datadog.android.core.internal.DatadogCore import com.datadog.android.core.internal.system.AppVersionProvider -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.StringForgery @@ -56,7 +56,7 @@ internal class InternalProxyTest { verify(mockRumFeatureScope).sendEvent(capture()) val payload = firstValue assert(payload["type"] == "telemetry_event") - val logEvent = payload["event"] as TelemetryEvent.Log.Debug + val logEvent = payload["event"] as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(message) } } @@ -81,7 +81,7 @@ internal class InternalProxyTest { verify(mockRumFeatureScope).sendEvent(capture()) val payload = firstValue assert(payload["type"] == "telemetry_event") - val logEvent = payload["event"] as TelemetryEvent.Log.Error + val logEvent = payload["event"] as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(message) assertThat(logEvent.stacktrace).isEqualTo(stack) assertThat(logEvent.kind).isEqualTo(kind) @@ -107,7 +107,7 @@ internal class InternalProxyTest { verify(mockRumFeatureScope).sendEvent(capture()) val payload = firstValue assert(payload["type"] == "telemetry_event") - val logEvent = payload["event"] as TelemetryEvent.Log.Error + val logEvent = payload["event"] as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(message) assertThat(logEvent.error).isEqualTo(throwable) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt index 41f3d2c53d..791cd2b8dd 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt @@ -20,6 +20,7 @@ import com.datadog.android.core.internal.DatadogCore import com.datadog.android.core.internal.SdkFeature import com.datadog.android.core.thread.FlushableExecutorService import com.datadog.android.error.internal.CrashReportsFeature +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.privacy.TrackingConsent import com.datadog.android.security.Encryption import com.datadog.android.utils.config.ApplicationContextTestConfiguration @@ -48,6 +49,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doAnswer import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -371,19 +373,24 @@ internal class DatadogCoreInitializationTest { } testedCore.coreFeature.uploadExecutorService.shutdownNow() - verify(mockRumFeature) - .sendEvent( - mapOf( - "type" to "telemetry_configuration", - "use_proxy" to useProxy, - "use_local_encryption" to useLocalEncryption, - "batch_size" to batchSize.windowDurationMs, - "batch_upload_frequency" to uploadFrequency.baseStepMs, - "track_errors" to trackErrors, - "batch_processing_level" to batchProcessingLevel.maxBatchesPerUploadJob, - "use_persistence_strategy_factory" to usePersistenceStrategyFactory - ) - ) + argumentCaptor> { + verify(mockRumFeature).sendEvent(capture()) + assertThat(firstValue.size).isEqualTo(2) + assertThat(firstValue["type"]).isEqualTo("telemetry_event") + val telemetryConfigurationEvent = firstValue["event"] as InternalTelemetryEvent.Configuration + assertThat(telemetryConfigurationEvent.trackErrors) + .isEqualTo(configuration.crashReportsEnabled) + assertThat(telemetryConfigurationEvent.batchSize) + .isEqualTo(configuration.coreConfig.batchSize.windowDurationMs) + assertThat(telemetryConfigurationEvent.useLocalEncryption) + .isEqualTo(configuration.coreConfig.encryption != null) + assertThat(telemetryConfigurationEvent.batchUploadFrequency) + .isEqualTo(configuration.coreConfig.uploadFrequency.baseStepMs) + assertThat(telemetryConfigurationEvent.batchProcessingLevel) + .isEqualTo(configuration.coreConfig.batchProcessingLevel.maxBatchesPerUploadJob) + assertThat(telemetryConfigurationEvent.useProxy) + .isEqualTo(configuration.coreConfig.proxy != null) + } } // region AdditionalConfig diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt index 8cb119a3c7..010a48bd58 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreTest.kt @@ -29,7 +29,6 @@ import com.datadog.android.core.internal.time.NoOpTimeProvider import com.datadog.android.core.internal.time.TimeProvider import com.datadog.android.core.internal.user.MutableUserInfoProvider import com.datadog.android.core.thread.FlushableExecutorService -import com.datadog.android.internal.telemetry.TelemetryEvent import com.datadog.android.ndk.internal.NdkCrashHandler import com.datadog.android.privacy.TrackingConsent import com.datadog.android.utils.config.ApplicationContextTestConfiguration @@ -80,7 +79,6 @@ import java.util.Collections import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.Future -import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference @@ -159,53 +157,6 @@ internal class DatadogCoreTest { verify(mockConsentProvider).setConsent(fakeConsent) } - @Test - fun `M send configuration telemetry W initialize()`() { - // Given - val mockRumFeature = mock() - testedCore = DatadogCore( - appContext.mockInstance, - fakeInstanceId, - fakeInstanceName, - internalLoggerProvider = { mockInternalLogger }, - executorServiceFactory = { _, _, _ -> mockPersistenceExecutorService }, - buildSdkVersionProvider = mockBuildSdkVersionProvider - ) - val mockUploadExecutorService = mock { executor -> - whenever(executor.schedule(any(), any(), any())) doAnswer { invocation -> - val runnable = invocation.getArgument(0) - runnable.run() - mock() - } - } - testedCore.features[Feature.RUM_FEATURE_NAME] = mockRumFeature - testedCore.initialize(fakeConfiguration) - - // When - testedCore.coreFeature.uploadExecutorService = mockUploadExecutorService - testedCore.sendCoreConfigurationTelemetryEvent(fakeConfiguration) - - // Then - argumentCaptor> { - verify(mockRumFeature).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val telemetryConfigurationEvent = firstValue["event"] as TelemetryEvent.Configuration - assertThat(telemetryConfigurationEvent.trackErrors) - .isEqualTo(fakeConfiguration.crashReportsEnabled) - assertThat(telemetryConfigurationEvent.batchSize) - .isEqualTo(fakeConfiguration.coreConfig.batchSize.windowDurationMs) - assertThat(telemetryConfigurationEvent.useLocalEncryption) - .isEqualTo(fakeConfiguration.coreConfig.encryption != null) - assertThat(telemetryConfigurationEvent.batchUploadFrequency) - .isEqualTo(fakeConfiguration.coreConfig.uploadFrequency.baseStepMs) - assertThat(telemetryConfigurationEvent.batchProcessingLevel) - .isEqualTo(fakeConfiguration.coreConfig.batchProcessingLevel.maxBatchesPerUploadJob) - assertThat(telemetryConfigurationEvent.useProxy) - .isEqualTo(fakeConfiguration.coreConfig.proxy != null) - } - } - @Test fun `M register feature W registerFeature()`( @Mock mockFeature: Feature, diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt index 9cb82e55c1..45805cb33f 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt @@ -14,7 +14,7 @@ import com.datadog.android.api.feature.FeatureScope import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.internal.metrics.MethodCalledTelemetry import com.datadog.android.core.metrics.TelemetryMetricType -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.utils.forge.Configurator import com.datadog.tools.unit.forge.aThrowable import com.datadog.tools.unit.forge.exhaustiveAttributes @@ -287,7 +287,7 @@ internal class SdkInternalLoggerTest { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isNull() } @@ -322,7 +322,7 @@ internal class SdkInternalLoggerTest { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) } @@ -354,7 +354,7 @@ internal class SdkInternalLoggerTest { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isEmpty() } @@ -387,7 +387,7 @@ internal class SdkInternalLoggerTest { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as TelemetryEvent.Log.Error + val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) } @@ -421,7 +421,7 @@ internal class SdkInternalLoggerTest { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as TelemetryEvent.Log.Error + val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.error).isEqualTo(fakeThrowable) assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) @@ -456,7 +456,7 @@ internal class SdkInternalLoggerTest { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as TelemetryEvent.Log.Debug + val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) } } @@ -485,7 +485,7 @@ internal class SdkInternalLoggerTest { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val metricEvent = firstValue["event"] as TelemetryEvent.Metric + val metricEvent = firstValue["event"] as InternalTelemetryEvent.Metric assertThat(metricEvent.message).isEqualTo(fakeMessage) assertThat(metricEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) } @@ -568,29 +568,29 @@ internal class SdkInternalLoggerTest { @Test fun `M send api usage telemetry W logApiUsage() { sampling rate 100 percent }`( - @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + @Forgery fakeApiUsageInternalTelemetryEvent: InternalTelemetryEvent.ApiUsage ) { // Given val mockRumFeatureScope = mock() whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope // When - testedInternalLogger.logApiUsage(fakeApiUsageTelemetryEvent, 100.0f) + testedInternalLogger.logApiUsage(fakeApiUsageInternalTelemetryEvent, 100.0f) // Then argumentCaptor>() { verify(mockRumFeatureScope).sendEvent(capture()) assertThat(firstValue.size).isEqualTo(2) assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val apiUsageEvent = firstValue["event"] as TelemetryEvent.ApiUsage - assertThat(apiUsageEvent).isEqualTo(fakeApiUsageTelemetryEvent) + val apiUsageEvent = firstValue["event"] as InternalTelemetryEvent.ApiUsage + assertThat(apiUsageEvent).isEqualTo(fakeApiUsageInternalTelemetryEvent) } } @Test fun `M send api usage telemetry W metric() {sampling x percent}`( @FloatForgery(25f, 75f) fakeSampleRate: Float, - @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + @Forgery fakeApiUsageInternalTelemetryEvent: InternalTelemetryEvent.ApiUsage ) { // Given val mockRumFeatureScope = mock() @@ -602,7 +602,7 @@ internal class SdkInternalLoggerTest { // When repeat(100) { testedInternalLogger.logApiUsage( - fakeApiUsageTelemetryEvent, + fakeApiUsageInternalTelemetryEvent, fakeSampleRate ) } @@ -614,7 +614,7 @@ internal class SdkInternalLoggerTest { @Test fun `M not send any api usage telemetry W logApiUsage() {sampling 0 percent}`( - @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + @Forgery fakeApiUsageInternalTelemetryEvent: InternalTelemetryEvent.ApiUsage ) { // Given val mockRumFeatureScope = mock() @@ -622,7 +622,7 @@ internal class SdkInternalLoggerTest { // When testedInternalLogger.logApiUsage( - fakeApiUsageTelemetryEvent, + fakeApiUsageInternalTelemetryEvent, 0.0f ) @@ -633,7 +633,7 @@ internal class SdkInternalLoggerTest { @Test fun `M do nothing W logApiUsage { rum feature not initialized }`( @FloatForgery(0f, 100f) fakeSampleRate: Float, - @Forgery fakeApiUsageTelemetryEvent: TelemetryEvent.ApiUsage + @Forgery fakeApiUsageInternalTelemetryEvent: InternalTelemetryEvent.ApiUsage ) { // Given whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn null @@ -641,7 +641,7 @@ internal class SdkInternalLoggerTest { // When assertDoesNotThrow { testedInternalLogger.logApiUsage( - fakeApiUsageTelemetryEvent, + fakeApiUsageInternalTelemetryEvent, fakeSampleRate ) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt index cf55cb6c31..e9c52e9cd2 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/utils/forge/Configurator.kt @@ -6,7 +6,7 @@ package com.datadog.android.utils.forge -import com.datadog.android.internal.tests.elmyr.TelemetryApiUsageEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.InternalTelemetryApiUsageForgeryFactory import com.datadog.android.test.elmyr.PersistenceStrategyBatchForgeryFactory import com.datadog.android.tests.elmyr.useCoreFactories import com.datadog.tools.unit.forge.BaseConfigurator @@ -73,6 +73,6 @@ internal class Configurator : forge.useJvmFactories() // telemetry - forge.addFactory(TelemetryApiUsageEventForgeryFactory()) + forge.addFactory(InternalTelemetryApiUsageForgeryFactory()) } } diff --git a/dd-sdk-android-internal/api/apiSurface b/dd-sdk-android-internal/api/apiSurface index 683725fde1..297181b61d 100644 --- a/dd-sdk-android-internal/api/apiSurface +++ b/dd-sdk-android-internal/api/apiSurface @@ -10,21 +10,21 @@ interface com.datadog.android.internal.profiler.BenchmarkTracer object com.datadog.android.internal.profiler.GlobalBenchmark fun register(BenchmarkProfiler) fun get(): BenchmarkProfiler -sealed class com.datadog.android.internal.telemetry.TelemetryEvent - sealed class Log : TelemetryEvent +sealed class com.datadog.android.internal.telemetry.InternalTelemetryEvent + sealed class Log : InternalTelemetryEvent constructor(String, Map?) class Debug : Log constructor(String, Map?) class Error : Log constructor(String, Map? = null, Throwable? = null, String? = null, String? = null) - data class Configuration : TelemetryEvent + data class Configuration : InternalTelemetryEvent constructor(Boolean, Long, Long, Boolean, Boolean, Int) - data class Metric : TelemetryEvent + data class Metric : InternalTelemetryEvent constructor(String, Map?) - sealed class ApiUsage : TelemetryEvent + sealed class ApiUsage : InternalTelemetryEvent constructor(MutableMap = mutableMapOf()) class AddViewLoadingTime : ApiUsage constructor(Boolean, Boolean, Boolean, MutableMap = mutableMapOf()) - object InterceptorInstantiated : TelemetryEvent + object InterceptorInstantiated : InternalTelemetryEvent annotation com.datadog.tools.annotation.NoOpImplementation constructor(Boolean = false) diff --git a/dd-sdk-android-internal/api/dd-sdk-android-internal.api b/dd-sdk-android-internal/api/dd-sdk-android-internal.api index 8a59b6bdcc..7663c761d1 100644 --- a/dd-sdk-android-internal/api/dd-sdk-android-internal.api +++ b/dd-sdk-android-internal/api/dd-sdk-android-internal.api @@ -29,16 +29,16 @@ public final class com/datadog/android/internal/profiler/GlobalBenchmark { public final fun register (Lcom/datadog/android/internal/profiler/BenchmarkProfiler;)V } -public abstract class com/datadog/android/internal/telemetry/TelemetryEvent { +public abstract class com/datadog/android/internal/telemetry/InternalTelemetryEvent { } -public abstract class com/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage : com/datadog/android/internal/telemetry/TelemetryEvent { +public abstract class com/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage : com/datadog/android/internal/telemetry/InternalTelemetryEvent { public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getAdditionalProperties ()Ljava/util/Map; } -public final class com/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage$AddViewLoadingTime : com/datadog/android/internal/telemetry/TelemetryEvent$ApiUsage { +public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage$AddViewLoadingTime : com/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage { public fun (ZZZLjava/util/Map;)V public synthetic fun (ZZZLjava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getNoActiveView ()Z @@ -46,7 +46,7 @@ public final class com/datadog/android/internal/telemetry/TelemetryEvent$ApiUsag public final fun getOverwrite ()Z } -public final class com/datadog/android/internal/telemetry/TelemetryEvent$Configuration : com/datadog/android/internal/telemetry/TelemetryEvent { +public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$Configuration : com/datadog/android/internal/telemetry/InternalTelemetryEvent { public fun (ZJJZZI)V public final fun component1 ()Z public final fun component2 ()J @@ -54,8 +54,8 @@ public final class com/datadog/android/internal/telemetry/TelemetryEvent$Configu public final fun component4 ()Z public final fun component5 ()Z public final fun component6 ()I - public final fun copy (ZJJZZI)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Configuration; - public static synthetic fun copy$default (Lcom/datadog/android/internal/telemetry/TelemetryEvent$Configuration;ZJJZZIILjava/lang/Object;)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Configuration; + public final fun copy (ZJJZZI)Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$Configuration; + public static synthetic fun copy$default (Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$Configuration;ZJJZZIILjava/lang/Object;)Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$Configuration; public fun equals (Ljava/lang/Object;)Z public final fun getBatchProcessingLevel ()I public final fun getBatchSize ()J @@ -67,21 +67,21 @@ public final class com/datadog/android/internal/telemetry/TelemetryEvent$Configu public fun toString ()Ljava/lang/String; } -public final class com/datadog/android/internal/telemetry/TelemetryEvent$InterceptorInstantiated : com/datadog/android/internal/telemetry/TelemetryEvent { - public static final field INSTANCE Lcom/datadog/android/internal/telemetry/TelemetryEvent$InterceptorInstantiated; +public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$InterceptorInstantiated : com/datadog/android/internal/telemetry/InternalTelemetryEvent { + public static final field INSTANCE Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$InterceptorInstantiated; } -public abstract class com/datadog/android/internal/telemetry/TelemetryEvent$Log : com/datadog/android/internal/telemetry/TelemetryEvent { +public abstract class com/datadog/android/internal/telemetry/InternalTelemetryEvent$Log : com/datadog/android/internal/telemetry/InternalTelemetryEvent { public synthetic fun (Ljava/lang/String;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getAdditionalProperties ()Ljava/util/Map; public final fun getMessage ()Ljava/lang/String; } -public final class com/datadog/android/internal/telemetry/TelemetryEvent$Log$Debug : com/datadog/android/internal/telemetry/TelemetryEvent$Log { +public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$Log$Debug : com/datadog/android/internal/telemetry/InternalTelemetryEvent$Log { public fun (Ljava/lang/String;Ljava/util/Map;)V } -public final class com/datadog/android/internal/telemetry/TelemetryEvent$Log$Error : com/datadog/android/internal/telemetry/TelemetryEvent$Log { +public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$Log$Error : com/datadog/android/internal/telemetry/InternalTelemetryEvent$Log { public fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/Throwable;Ljava/lang/String;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/util/Map;Ljava/lang/Throwable;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getError ()Ljava/lang/Throwable; @@ -89,12 +89,12 @@ public final class com/datadog/android/internal/telemetry/TelemetryEvent$Log$Err public final fun getStacktrace ()Ljava/lang/String; } -public final class com/datadog/android/internal/telemetry/TelemetryEvent$Metric : com/datadog/android/internal/telemetry/TelemetryEvent { +public final class com/datadog/android/internal/telemetry/InternalTelemetryEvent$Metric : com/datadog/android/internal/telemetry/InternalTelemetryEvent { public fun (Ljava/lang/String;Ljava/util/Map;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/util/Map; - public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Metric; - public static synthetic fun copy$default (Lcom/datadog/android/internal/telemetry/TelemetryEvent$Metric;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/internal/telemetry/TelemetryEvent$Metric; + public final fun copy (Ljava/lang/String;Ljava/util/Map;)Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$Metric; + public static synthetic fun copy$default (Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$Metric;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$Metric; public fun equals (Ljava/lang/Object;)Z public final fun getAdditionalProperties ()Ljava/util/Map; public final fun getMessage ()Ljava/lang/String; diff --git a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/TelemetryEvent.kt b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/InternalTelemetryEvent.kt similarity index 84% rename from dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/TelemetryEvent.kt rename to dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/InternalTelemetryEvent.kt index ce4968d284..839156c49d 100644 --- a/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/TelemetryEvent.kt +++ b/dd-sdk-android-internal/src/main/java/com/datadog/android/internal/telemetry/InternalTelemetryEvent.kt @@ -7,9 +7,9 @@ package com.datadog.android.internal.telemetry @Suppress("UndocumentedPublicClass", "UndocumentedPublicFunction", "UndocumentedPublicProperty") -sealed class TelemetryEvent { +sealed class InternalTelemetryEvent { - sealed class Log(val message: String, val additionalProperties: Map?) : TelemetryEvent() { + sealed class Log(val message: String, val additionalProperties: Map?) : InternalTelemetryEvent() { class Debug(message: String, additionalProperties: Map?) : Log(message, additionalProperties) class Error( @@ -28,14 +28,15 @@ sealed class TelemetryEvent { val useProxy: Boolean, val useLocalEncryption: Boolean, val batchProcessingLevel: Int - ) : TelemetryEvent() + ) : InternalTelemetryEvent() data class Metric( val message: String, val additionalProperties: Map? - ) : TelemetryEvent() + ) : InternalTelemetryEvent() - sealed class ApiUsage(val additionalProperties: MutableMap = mutableMapOf()) : TelemetryEvent() { + sealed class ApiUsage(val additionalProperties: MutableMap = mutableMapOf()) : + InternalTelemetryEvent() { class AddViewLoadingTime( val overwrite: Boolean, val noView: Boolean, @@ -44,5 +45,5 @@ sealed class TelemetryEvent { ) : ApiUsage(additionalProperties) } - object InterceptorInstantiated : TelemetryEvent() + object InterceptorInstantiated : InternalTelemetryEvent() } diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryApiUsageEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt similarity index 66% rename from dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryApiUsageEventForgeryFactory.kt rename to dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt index e6aab38029..c3b1db7114 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryApiUsageEventForgeryFactory.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt @@ -6,14 +6,14 @@ package com.datadog.android.internal.tests.elmyr -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory -class TelemetryApiUsageEventForgeryFactory : ForgeryFactory { +class InternalTelemetryApiUsageForgeryFactory : ForgeryFactory { - override fun getForgery(forge: Forge): TelemetryEvent.ApiUsage { - return TelemetryEvent.ApiUsage.AddViewLoadingTime( + override fun getForgery(forge: Forge): InternalTelemetryEvent.ApiUsage { + return InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( overwrite = forge.aBool(), noView = forge.aBool(), noActiveView = forge.aBool(), diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryInternalConfigurationEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryConfigurationForgeryFactory.kt similarity index 67% rename from dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryInternalConfigurationEventForgeryFactory.kt rename to dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryConfigurationForgeryFactory.kt index ec1c9fa683..657864a1c3 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryInternalConfigurationEventForgeryFactory.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryConfigurationForgeryFactory.kt @@ -6,14 +6,14 @@ package com.datadog.android.internal.tests.elmyr -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory -class TelemetryInternalConfigurationEventForgeryFactory : ForgeryFactory { +class InternalTelemetryConfigurationForgeryFactory : ForgeryFactory { - override fun getForgery(forge: Forge): TelemetryEvent.Configuration { - return TelemetryEvent.Configuration( + override fun getForgery(forge: Forge): InternalTelemetryEvent.Configuration { + return InternalTelemetryEvent.Configuration( trackErrors = forge.aBool(), batchSize = forge.aLong(), batchProcessingLevel = forge.anInt(), diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryMetricEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryDebugLogForgeryFactory.kt similarity index 63% rename from dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryMetricEventForgeryFactory.kt rename to dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryDebugLogForgeryFactory.kt index 54e573e256..13941b0754 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryMetricEventForgeryFactory.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryDebugLogForgeryFactory.kt @@ -6,14 +6,14 @@ package com.datadog.android.internal.tests.elmyr -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory -class TelemetryMetricEventForgeryFactory : ForgeryFactory { +class InternalTelemetryDebugLogForgeryFactory : ForgeryFactory { - override fun getForgery(forge: Forge): TelemetryEvent.Metric { - return TelemetryEvent.Metric( + override fun getForgery(forge: Forge): InternalTelemetryEvent.Log.Debug { + return InternalTelemetryEvent.Log.Debug( message = forge.aString(), additionalProperties = forge.aMap { forge.aString() to forge.aString() } ) diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogErrorEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryErrorLogForgeryFactory.kt similarity index 71% rename from dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogErrorEventForgeryFactory.kt rename to dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryErrorLogForgeryFactory.kt index e06429edd2..95dbb51c12 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogErrorEventForgeryFactory.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryErrorLogForgeryFactory.kt @@ -6,15 +6,15 @@ package com.datadog.android.internal.tests.elmyr -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.tools.unit.forge.aThrowable import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory -class TelemetryLogErrorEventForgeryFactory : ForgeryFactory { +class InternalTelemetryErrorLogForgeryFactory : ForgeryFactory { - override fun getForgery(forge: Forge): TelemetryEvent.Log.Error { - return TelemetryEvent.Log.Error( + override fun getForgery(forge: Forge): InternalTelemetryEvent.Log.Error { + return InternalTelemetryEvent.Log.Error( message = forge.aString(), additionalProperties = forge.aMap { forge.aString() to forge.aString() }, error = forge.aNullable { forge.aThrowable() }, diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryEventForgeryFactory.kt new file mode 100644 index 0000000000..6ffc07009b --- /dev/null +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryEventForgeryFactory.kt @@ -0,0 +1,28 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.internal.tests.elmyr + +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +class InternalTelemetryEventForgeryFactory : ForgeryFactory { + + override fun getForgery(forge: Forge): InternalTelemetryEvent { + val random = forge.anInt(min = 0, max = 6) + return when (random) { + 0 -> forge.getForgery() + + 1 -> forge.getForgery() + + 2 -> forge.getForgery() + 3 -> InternalTelemetryEvent.InterceptorInstantiated + 4 -> forge.getForgery() + else -> forge.getForgery() + } + } +} diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogDebugEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryMetricForgeryFactory.kt similarity index 64% rename from dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogDebugEventForgeryFactory.kt rename to dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryMetricForgeryFactory.kt index 2a22a46bf0..aaa5e5ad43 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryLogDebugEventForgeryFactory.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryMetricForgeryFactory.kt @@ -6,14 +6,14 @@ package com.datadog.android.internal.tests.elmyr -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory -class TelemetryLogDebugEventForgeryFactory : ForgeryFactory { +class InternalTelemetryMetricForgeryFactory : ForgeryFactory { - override fun getForgery(forge: Forge): TelemetryEvent.Log.Debug { - return TelemetryEvent.Log.Debug( + override fun getForgery(forge: Forge): InternalTelemetryEvent.Metric { + return InternalTelemetryEvent.Metric( message = forge.aString(), additionalProperties = forge.aMap { forge.aString() to forge.aString() } ) diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt deleted file mode 100644 index 721d796661..0000000000 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/TelemetryEventForgeryFactory.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2016-Present Datadog, Inc. - */ - -package com.datadog.android.internal.tests.elmyr - -import com.datadog.android.internal.telemetry.TelemetryEvent -import fr.xgouchet.elmyr.Forge -import fr.xgouchet.elmyr.ForgeryFactory - -class TelemetryEventForgeryFactory : ForgeryFactory { - - override fun getForgery(forge: Forge): TelemetryEvent { - val random = forge.anInt(min = 0, max = 6) - return when (random) { - 0 -> forge.getForgery() - - 1 -> forge.getForgery() - - 2 -> forge.getForgery() - 3 -> TelemetryEvent.InterceptorInstantiated - 4 -> forge.getForgery() - else -> forge.getForgery() - } - } -} diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 4fe900bf4d..827a2263f9 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -33,7 +33,7 @@ import com.datadog.android.core.internal.utils.submitSafe import com.datadog.android.event.EventMapper import com.datadog.android.event.MapperSerializer import com.datadog.android.event.NoOpEventMapper -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumSessionListener @@ -293,7 +293,7 @@ internal class RumFeature( // region Internal private fun handleTelemetryEvent(event: Map<*, *>) { - val telemetryEvent = event[EVENT_MESSAGE_KEY] as? TelemetryEvent + val telemetryEvent = event[EVENT_MESSAGE_KEY] as? InternalTelemetryEvent val advancedRumMonitor = GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor ?: return if (telemetryEvent != null) { advancedRumMonitor.sendTelemetryEvent(telemetryEvent) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt index a6f29b0467..63047d50b7 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEvent.kt @@ -7,7 +7,7 @@ package com.datadog.android.rum.internal.domain.scope import com.datadog.android.core.feature.event.ThreadDump -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumPerformanceMetric @@ -217,7 +217,7 @@ internal sealed class RumRawEvent { internal data class WebViewEvent(override val eventTime: Time = Time()) : RumRawEvent() internal data class TelemetryEventWrapper( - val event: TelemetryEvent, + val event: InternalTelemetryEvent, override val eventTime: Time = Time() ) : RumRawEvent() diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt index 585d1ff414..4781c2e0a5 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/AdvancedRumMonitor.kt @@ -7,7 +7,7 @@ package com.datadog.android.rum.internal.monitor import com.datadog.android.core.feature.event.ThreadDump -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.RumMonitor import com.datadog.android.rum.RumPerformanceMetric @@ -42,7 +42,7 @@ internal interface AdvancedRumMonitor : RumMonitor, AdvancedNetworkRumMonitor { fun setDebugListener(listener: RumDebugListener?) - fun sendTelemetryEvent(telemetryEvent: TelemetryEvent) + fun sendTelemetryEvent(telemetryEvent: InternalTelemetryEvent) fun updatePerformanceMetric(metric: RumPerformanceMetric, value: Double) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt index 3896153622..4b29a593eb 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt @@ -15,7 +15,7 @@ import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.utils.submitSafe -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumActionType @@ -587,7 +587,7 @@ internal class DatadogRumMonitor( override fun notifyInterceptorInstantiated() { handleEvent( - RumRawEvent.TelemetryEventWrapper(TelemetryEvent.InterceptorInstantiated) + RumRawEvent.TelemetryEventWrapper(InternalTelemetryEvent.InterceptorInstantiated) ) } @@ -606,7 +606,7 @@ internal class DatadogRumMonitor( return internalProxy } - override fun sendTelemetryEvent(telemetryEvent: TelemetryEvent) { + override fun sendTelemetryEvent(telemetryEvent: InternalTelemetryEvent) { handleEvent(RumRawEvent.TelemetryEventWrapper(telemetryEvent)) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 8464d1262e..4232a45cff 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -15,7 +15,7 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.RateBasedSampler import com.datadog.android.core.sampling.Sampler -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.domain.RumContext @@ -60,7 +60,7 @@ internal class TelemetryEventHandler( sdkCore.getFeature(Feature.RUM_FEATURE_NAME)?.withWriteContext { datadogContext, eventBatchWriter -> val timestamp = wrappedEvent.eventTime.timestamp + datadogContext.time.serverTimeOffsetMs val telemetryEvent: Any? = when (event) { - is TelemetryEvent.Log.Debug -> { + is InternalTelemetryEvent.Log.Debug -> { createDebugEvent( datadogContext = datadogContext, timestamp = timestamp, @@ -69,7 +69,7 @@ internal class TelemetryEventHandler( ) } - is TelemetryEvent.Metric -> { + is InternalTelemetryEvent.Metric -> { createDebugEvent( datadogContext = datadogContext, timestamp = timestamp, @@ -78,7 +78,7 @@ internal class TelemetryEventHandler( ) } - is TelemetryEvent.Log.Error -> { + is InternalTelemetryEvent.Log.Error -> { sessionEndedMetricDispatcher.onSdkErrorTracked( sessionId = datadogContext.rumContext().sessionId, errorKind = event.kind @@ -93,7 +93,7 @@ internal class TelemetryEventHandler( ) } - is TelemetryEvent.Configuration -> { + is InternalTelemetryEvent.Configuration -> { createConfigurationEvent( datadogContext, timestamp, @@ -101,7 +101,7 @@ internal class TelemetryEventHandler( ) } - is TelemetryEvent.ApiUsage -> { + is InternalTelemetryEvent.ApiUsage -> { createApiUsageEvent( datadogContext = datadogContext, timestamp = timestamp, @@ -109,7 +109,7 @@ internal class TelemetryEventHandler( ) } - is TelemetryEvent.InterceptorInstantiated -> { + is InternalTelemetryEvent.InterceptorInstantiated -> { trackNetworkRequests = true null } @@ -130,16 +130,16 @@ internal class TelemetryEventHandler( // region private @Suppress("ReturnCount") - private fun canWrite(event: TelemetryEvent): Boolean { + private fun canWrite(event: InternalTelemetryEvent): Boolean { if (!eventSampler.sample()) return false - if (event is TelemetryEvent.Configuration && !configurationExtraSampler.sample()) { + if (event is InternalTelemetryEvent.Configuration && !configurationExtraSampler.sample()) { return false } val eventIdentity = event.identity - if (event !is TelemetryEvent.Metric && eventIDsSeenInCurrentSession.contains(eventIdentity)) { + if (event !is InternalTelemetryEvent.Metric && eventIDsSeenInCurrentSession.contains(eventIdentity)) { sdkCore.internalLogger.log( InternalLogger.Level.INFO, InternalLogger.Target.MAINTAINER, @@ -253,7 +253,7 @@ internal class TelemetryEventHandler( private fun createConfigurationEvent( datadogContext: DatadogContext, timestamp: Long, - event: TelemetryEvent.Configuration + event: InternalTelemetryEvent.Configuration ): TelemetryConfigurationEvent { val traceFeature = sdkCore.getFeature(Feature.TRACING_FEATURE_NAME) val sessionReplayFeatureContext = @@ -341,11 +341,11 @@ internal class TelemetryEventHandler( private fun createApiUsageEvent( datadogContext: DatadogContext, timestamp: Long, - event: TelemetryEvent.ApiUsage + event: InternalTelemetryEvent.ApiUsage ): TelemetryUsageEvent? { val rumContext = datadogContext.rumContext() return when (event) { - is TelemetryEvent.ApiUsage.AddViewLoadingTime -> { + is InternalTelemetryEvent.ApiUsage.AddViewLoadingTime -> { TelemetryUsageEvent( dd = TelemetryUsageEvent.Dd(), date = timestamp, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt index cce1077eb5..1e3e5d0475 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventId.kt @@ -6,7 +6,7 @@ package com.datadog.android.telemetry.internal -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent internal data class TelemetryEventId( val type: TelemetryType, @@ -14,22 +14,22 @@ internal data class TelemetryEventId( val kind: String? ) -internal val TelemetryEvent.identity: TelemetryEventId +internal val InternalTelemetryEvent.identity: TelemetryEventId get() { return when (this) { - is TelemetryEvent.Log.Error -> TelemetryEventId(type(), message, kind) - is TelemetryEvent.Log.Debug -> TelemetryEventId(type(), message, null) + is InternalTelemetryEvent.Log.Error -> TelemetryEventId(type(), message, kind) + is InternalTelemetryEvent.Log.Debug -> TelemetryEventId(type(), message, null) else -> TelemetryEventId(type(), "", null) } } -internal fun TelemetryEvent.type(): TelemetryType { +internal fun InternalTelemetryEvent.type(): TelemetryType { return when (this) { - is TelemetryEvent.Log.Debug -> TelemetryType.DEBUG - is TelemetryEvent.Log.Error -> TelemetryType.ERROR - is TelemetryEvent.Configuration -> TelemetryType.CONFIGURATION - is TelemetryEvent.Metric -> TelemetryType.METRIC - is TelemetryEvent.ApiUsage -> TelemetryType.API_USAGE - is TelemetryEvent.InterceptorInstantiated -> TelemetryType.INTERCEPTOR_SETUP + is InternalTelemetryEvent.Log.Debug -> TelemetryType.DEBUG + is InternalTelemetryEvent.Log.Error -> TelemetryType.ERROR + is InternalTelemetryEvent.Configuration -> TelemetryType.CONFIGURATION + is InternalTelemetryEvent.Metric -> TelemetryType.METRIC + is InternalTelemetryEvent.ApiUsage -> TelemetryType.API_USAGE + is InternalTelemetryEvent.InterceptorInstantiated -> TelemetryType.INTERCEPTOR_SETUP } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 1f7dcc8a66..4e935756a0 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -19,7 +19,7 @@ import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.event.EventMapper import com.datadog.android.event.MapperSerializer -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.assertj.RumFeatureAssert @@ -1198,20 +1198,20 @@ internal class RumFeatureTest { @Test fun `M handle telemetry event W onReceive()`( - @Forgery fakeTelemetryEvent: TelemetryEvent + @Forgery fakeInternalTelemetryEvent: InternalTelemetryEvent ) { // Given testedFeature.onInitialize(appContext.mockInstance) val event = mapOf( "type" to RumFeature.TELEMETRY_EVENT_MESSAGE_TYPE, - "event" to fakeTelemetryEvent + "event" to fakeInternalTelemetryEvent ) // When testedFeature.onReceive(event) // Then - verify(mockRumMonitor).sendTelemetryEvent(fakeTelemetryEvent) + verify(mockRumMonitor).sendTelemetryEvent(fakeInternalTelemetryEvent) verifyNoMoreInteractions(mockRumMonitor) verifyNoInteractions(mockInternalLogger) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt index 326945cd73..8c6a21f7ba 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/metric/FakeInternalLogger.kt @@ -9,7 +9,7 @@ package com.datadog.android.rum.internal.metric import com.datadog.android.api.InternalLogger import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent class FakeInternalLogger : InternalLogger { @@ -53,7 +53,7 @@ class FakeInternalLogger : InternalLogger { return null } - override fun logApiUsage(apiUsageEvent: TelemetryEvent.ApiUsage, samplingRate: Float) { + override fun logApiUsage(apiUsageEvent: InternalTelemetryEvent.ApiUsage, samplingRate: Float) { // do nothing } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt index c69310ec06..7d82f4ee68 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitorTest.kt @@ -16,7 +16,7 @@ import com.datadog.android.api.storage.DataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.RumActionType @@ -1860,9 +1860,9 @@ internal class DatadogRumMonitorTest { } @Test - fun `M handle telemetry event W sendTelemetryEvent()`(@Forgery fakeTelemetryEvent: TelemetryEvent) { + fun `M handle telemetry event W sendTelemetryEvent()`(@Forgery fakeInternalTelemetryEvent: InternalTelemetryEvent) { // When - testedMonitor.sendTelemetryEvent(fakeTelemetryEvent) + testedMonitor.sendTelemetryEvent(fakeInternalTelemetryEvent) // Then argumentCaptor { @@ -1870,7 +1870,7 @@ internal class DatadogRumMonitorTest { capture(), eq(mockWriter) ) - assertThat(lastValue.event).isEqualTo(fakeTelemetryEvent) + assertThat(lastValue.event).isEqualTo(fakeInternalTelemetryEvent) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt index d60448bf17..a94c70b747 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt @@ -6,12 +6,12 @@ package com.datadog.android.rum.utils.forge -import com.datadog.android.internal.tests.elmyr.TelemetryApiUsageEventForgeryFactory -import com.datadog.android.internal.tests.elmyr.TelemetryEventForgeryFactory -import com.datadog.android.internal.tests.elmyr.TelemetryInternalConfigurationEventForgeryFactory -import com.datadog.android.internal.tests.elmyr.TelemetryLogDebugEventForgeryFactory -import com.datadog.android.internal.tests.elmyr.TelemetryLogErrorEventForgeryFactory -import com.datadog.android.internal.tests.elmyr.TelemetryMetricEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.InternalTelemetryApiUsageForgeryFactory +import com.datadog.android.internal.tests.elmyr.InternalTelemetryConfigurationForgeryFactory +import com.datadog.android.internal.tests.elmyr.InternalTelemetryDebugLogForgeryFactory +import com.datadog.android.internal.tests.elmyr.InternalTelemetryErrorLogForgeryFactory +import com.datadog.android.internal.tests.elmyr.InternalTelemetryEventForgeryFactory +import com.datadog.android.internal.tests.elmyr.InternalTelemetryMetricForgeryFactory import com.datadog.android.rum.tests.elmyr.ResourceIdForgeryFactory import com.datadog.android.rum.tests.elmyr.RumScopeKeyForgeryFactory import com.datadog.android.tests.elmyr.useCoreFactories @@ -52,11 +52,11 @@ internal class Configurator : BaseConfigurator() { forge.addFactory(TelemetryConfigurationEventForgeryFactory()) // Telemetry internal models - forge.addFactory(TelemetryEventForgeryFactory()) - forge.addFactory(TelemetryMetricEventForgeryFactory()) - forge.addFactory(TelemetryLogDebugEventForgeryFactory()) - forge.addFactory(TelemetryLogErrorEventForgeryFactory()) - forge.addFactory(TelemetryInternalConfigurationEventForgeryFactory()) - forge.addFactory(TelemetryApiUsageEventForgeryFactory()) + forge.addFactory(InternalTelemetryEventForgeryFactory()) + forge.addFactory(InternalTelemetryMetricForgeryFactory()) + forge.addFactory(InternalTelemetryDebugLogForgeryFactory()) + forge.addFactory(InternalTelemetryErrorLogForgeryFactory()) + forge.addFactory(InternalTelemetryConfigurationForgeryFactory()) + forge.addFactory(InternalTelemetryApiUsageForgeryFactory()) } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index 769e9207d5..64d33b599d 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -16,7 +16,7 @@ import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.Sampler -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.scope.RumRawEvent @@ -45,6 +45,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions @@ -195,7 +196,7 @@ internal class TelemetryEventHandlerTest { // region Debug Event @Test - fun `M create debug event W handleEvent(Log Debug)`(@Forgery fakeLogDebugEvent: TelemetryEvent.Log.Debug) { + fun `M create debug event W handleEvent(Log Debug)`(@Forgery fakeLogDebugEvent: InternalTelemetryEvent.Log.Debug) { // Given val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeLogDebugEvent) @@ -215,7 +216,9 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M create debug event W handleEvent(Log Debug, no RUM)`(@Forgery fakeLogDebugEvent: TelemetryEvent.Log.Debug) { + fun `M create debug event W handleEvent(Log Debug, no RUM)`( + @Forgery fakeLogDebugEvent: InternalTelemetryEvent.Log.Debug + ) { // Given val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeLogDebugEvent) fakeDatadogContext = fakeDatadogContext.copy( @@ -250,7 +253,7 @@ internal class TelemetryEventHandlerTest { // region Error Event @Test - fun `M create error event W handleEvent(Log Error)`(@Forgery fakeLogErrorEvent: TelemetryEvent.Log.Error) { + fun `M create error event W handleEvent(Log Error)`(@Forgery fakeLogErrorEvent: InternalTelemetryEvent.Log.Error) { // Given val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeLogErrorEvent) fakeDatadogContext = fakeDatadogContext.copy( @@ -286,7 +289,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent(Configuration)`( - @Forgery fakeConfigurationEvent: TelemetryEvent.Configuration + @Forgery fakeConfigurationEvent: InternalTelemetryEvent.Configuration ) { // Given val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeConfigurationEvent) @@ -308,7 +311,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent() { Configuration, no RUM)`( - @Forgery fakeConfigurationEvent: TelemetryEvent.Configuration + @Forgery fakeConfigurationEvent: InternalTelemetryEvent.Configuration ) { // Given val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeConfigurationEvent) @@ -342,7 +345,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent() {Configuration, with RUM config }`( @Forgery fakeRumConfiguration: RumFeature.Configuration, - @Forgery fakeConfigurationEvent: TelemetryEvent.Configuration + @Forgery fakeConfigurationEvent: InternalTelemetryEvent.Configuration ) { // Given val mockRumFeature = mock() @@ -390,7 +393,7 @@ internal class TelemetryEventHandlerTest { useTracer: Boolean, tracerApi: TelemetryEventHandler.TracerApi?, tracerApiVersion: String?, - @Forgery fakeConfiguration: TelemetryEvent.Configuration + @Forgery fakeConfiguration: InternalTelemetryEvent.Configuration ) { // Given val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) @@ -428,7 +431,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent() { configuration with interceptor }`( - @Forgery fakeConfiguration: TelemetryEvent.Configuration, + @Forgery fakeConfiguration: InternalTelemetryEvent.Configuration, forge: Forge ) { // Given @@ -438,7 +441,7 @@ internal class TelemetryEventHandlerTest { // When if (trackNetworkRequests) { testedTelemetryHandler.handleEvent( - RumRawEvent.TelemetryEventWrapper(TelemetryEvent.InterceptorInstantiated), + RumRawEvent.TelemetryEventWrapper(InternalTelemetryEvent.InterceptorInstantiated), mockWriter ) } @@ -460,7 +463,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent() { configuration, no SessionReplay }`( - @Forgery fakeConfiguration: TelemetryEvent.Configuration + @Forgery fakeConfiguration: InternalTelemetryEvent.Configuration ) { // Given val configRawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) @@ -482,7 +485,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent() { configuration, with SessionReplay }`( - @Forgery fakeConfiguration: TelemetryEvent.Configuration, + @Forgery fakeConfiguration: InternalTelemetryEvent.Configuration, forge: Forge ) { // Given @@ -520,7 +523,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M create config event W handleEvent(SendTelemetry) { with SessionReplay, bad format }`( - @Forgery fakeConfiguration: TelemetryEvent.Configuration, + @Forgery fakeConfiguration: InternalTelemetryEvent.Configuration, forge: Forge ) { // Given @@ -556,10 +559,10 @@ internal class TelemetryEventHandlerTest { @Test fun `M not write event W handleEvent() { event is not sampled }`( - @Forgery fakeTelemetryEvent: TelemetryEvent + @Forgery fakeInternalTelemetryEvent: InternalTelemetryEvent ) { // Given - val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeTelemetryEvent) + val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeInternalTelemetryEvent) whenever(mockSampler.sample()) doReturn false // When @@ -575,8 +578,8 @@ internal class TelemetryEventHandlerTest { ) { // Given val logeEvent = forge.anElementFrom( - forge.getForgery(), - forge.getForgery() + forge.getForgery(), + forge.getForgery() ) val rawEvent = RumRawEvent.TelemetryEventWrapper(logeEvent) whenever(mockSampler.sample()) doReturn true @@ -588,7 +591,7 @@ internal class TelemetryEventHandlerTest { // Then argumentCaptor { verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) - if (logeEvent is TelemetryEvent.Log.Debug) { + if (logeEvent is InternalTelemetryEvent.Log.Debug) { assertThat(lastValue).isInstanceOf(TelemetryDebugEvent::class.java) } else { assertThat(lastValue).isInstanceOf(TelemetryErrorEvent::class.java) @@ -598,7 +601,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M not write configuration event W handleEvent() { event is not sampled }`( - @Forgery fakeConfiguration: TelemetryEvent.Configuration + @Forgery fakeConfiguration: InternalTelemetryEvent.Configuration ) { // Given val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeConfiguration) @@ -617,13 +620,12 @@ internal class TelemetryEventHandlerTest { forge: Forge ) { // Given - val telemetryEvent = forge.anElementFrom( - forge.getForgery(), - forge.getForgery(), - forge.getForgery(), - TelemetryEvent.InterceptorInstantiated + val internalTelemetryEvent = forge.anElementFrom( + forge.getForgery(), + forge.getForgery(), + forge.getForgery(), ) - val rawEvent = RumRawEvent.TelemetryEventWrapper(telemetryEvent) + val rawEvent = RumRawEvent.TelemetryEventWrapper(internalTelemetryEvent) val anotherEvent = rawEvent.copy() // When @@ -636,7 +638,7 @@ internal class TelemetryEventHandlerTest { InternalLogger.Target.MAINTAINER, TelemetryEventHandler.ALREADY_SEEN_EVENT_MESSAGE.format( Locale.US, - telemetryEvent.identity + internalTelemetryEvent.identity ) ) @@ -646,7 +648,7 @@ internal class TelemetryEventHandlerTest { is TelemetryDebugEvent -> { assertDebugEventMatchesInternalEvent( capturedValue, - telemetryEvent as TelemetryEvent.Log.Debug, + internalTelemetryEvent as InternalTelemetryEvent.Log.Debug, fakeRumContext, rawEvent.eventTime.timestamp ) @@ -655,7 +657,7 @@ internal class TelemetryEventHandlerTest { is TelemetryErrorEvent -> { assertErrorEventMatchesInternalEvent( capturedValue, - telemetryEvent as TelemetryEvent.Log.Error, + internalTelemetryEvent as InternalTelemetryEvent.Log.Error, fakeRumContext, rawEvent.eventTime.timestamp ) @@ -664,14 +666,14 @@ internal class TelemetryEventHandlerTest { is TelemetryConfigurationEvent -> { assertConfigEventMatchesInternalEvent( capturedValue, - telemetryEvent as TelemetryEvent.Configuration, + internalTelemetryEvent as InternalTelemetryEvent.Configuration, fakeRumContext, rawEvent.eventTime.timestamp ) } - is TelemetryEvent.InterceptorInstantiated -> { - assertThat(capturedValue).isEqualTo(TelemetryEvent.InterceptorInstantiated) + is InternalTelemetryEvent.InterceptorInstantiated -> { + assertThat(capturedValue).isEqualTo(InternalTelemetryEvent.InterceptorInstantiated) } else -> throw IllegalArgumentException( @@ -684,7 +686,7 @@ internal class TelemetryEventHandlerTest { @Test fun `M write event W handleEvent(){ seen in the session, is metric }`( - @Forgery fakeMetricEvent: TelemetryEvent.Metric + @Forgery fakeMetricEvent: InternalTelemetryEvent.Metric ) { // Given val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeMetricEvent) @@ -716,7 +718,7 @@ internal class TelemetryEventHandlerTest { fun `M not write events over the limit W handleEvent() { metric event }`( forge: Forge ) { - val events = (0..MAX_EVENTS_PER_SESSION_TEST).map { forge.getForgery() } + val events = (0..MAX_EVENTS_PER_SESSION_TEST).map { forge.getForgery() } // When events.forEach { @@ -736,10 +738,10 @@ internal class TelemetryEventHandlerTest { // Given // important because non-metric events can only be seen once val eventsInOldSession = (0..MAX_EVENTS_PER_SESSION_TEST / 2).map { - forge.getForgery() + forge.getForgery() }.map { RumRawEvent.TelemetryEventWrapper(it) } val eventsInNewSession = (0..MAX_EVENTS_PER_SESSION_TEST / 2).map { - forge.getForgery() + forge.getForgery() }.map { RumRawEvent.TelemetryEventWrapper(it) } eventsInOldSession.forEach { @@ -771,7 +773,7 @@ internal class TelemetryEventHandlerTest { val events = forge.aList( size = MAX_EVENTS_PER_SESSION_TEST * 10 - ) { forge.getForgery() } + ) { forge.getForgery() } // remove unwanted identity collisions .groupBy { it.identity } .map { RumRawEvent.TelemetryEventWrapper(it.value.first()) } @@ -797,7 +799,7 @@ internal class TelemetryEventHandlerTest { private fun assertDebugEventMatchesInternalEvent( actual: TelemetryDebugEvent, - internalDebugEvent: TelemetryEvent.Log.Debug, + internalDebugEvent: InternalTelemetryEvent.Log.Debug, rumContext: RumContext, time: Long ) { @@ -822,7 +824,7 @@ internal class TelemetryEventHandlerTest { private fun assertDebugEventMatchesMetricInternalEvent( actual: TelemetryDebugEvent, - internalMetricEvent: TelemetryEvent.Metric, + internalMetricEvent: InternalTelemetryEvent.Metric, rumContext: RumContext, time: Long ) { @@ -847,7 +849,7 @@ internal class TelemetryEventHandlerTest { private fun assertErrorEventMatchesInternalEvent( actual: TelemetryErrorEvent, - internalErrorEvent: TelemetryEvent.Log.Error, + internalErrorEvent: InternalTelemetryEvent.Log.Error, rumContext: RumContext, time: Long ) { @@ -874,7 +876,7 @@ internal class TelemetryEventHandlerTest { private fun assertConfigEventMatchesInternalEvent( actual: TelemetryConfigurationEvent, - internalConfigurationEvent: TelemetryEvent.Configuration, + internalConfigurationEvent: InternalTelemetryEvent.Configuration, rumContext: RumContext, time: Long ) { @@ -897,7 +899,7 @@ internal class TelemetryEventHandlerTest { private fun assertConfigEventMatchesInternalEvent( actual: TelemetryConfigurationEvent, - internalConfigurationEvent: TelemetryEvent.Configuration, + internalConfigurationEvent: InternalTelemetryEvent.Configuration, time: Long ) { assertThat(actual) diff --git a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt index a0a59c872c..b854ac6529 100644 --- a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt +++ b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt @@ -9,7 +9,7 @@ package com.datadog.android.core.stub import com.datadog.android.api.InternalLogger import com.datadog.android.core.metrics.PerformanceMetric import com.datadog.android.core.metrics.TelemetryMetricType -import com.datadog.android.internal.telemetry.TelemetryEvent +import com.datadog.android.internal.telemetry.InternalTelemetryEvent @Suppress("UnsafeThirdPartyFunctionCall") internal class StubInternalLogger : InternalLogger { @@ -65,7 +65,7 @@ internal class StubInternalLogger : InternalLogger { } override fun logApiUsage( - apiUsageEvent: TelemetryEvent.ApiUsage, + apiUsageEvent: InternalTelemetryEvent.ApiUsage, samplingRate: Float ) { println("AU [T]: ${apiUsageEvent::class.simpleName} | $samplingRate%") From c17f90afad6c05e407e342c0b3a95198c102fb66 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Mon, 16 Sep 2024 11:24:03 +0200 Subject: [PATCH 027/111] Apply code review changes --- .../com/datadog/android/_InternalProxy.kt | 17 +- .../android/core/internal/DatadogCore.kt | 7 +- .../core/internal/logger/SdkInternalLogger.kt | 16 +- .../com/datadog/android/InternalProxyTest.kt | 18 +- .../core/DatadogCoreInitializationTest.kt | 6 +- .../internal/logger/SdkInternalLoggerTest.kt | 48 ++-- ...InternalTelemetryApiUsageForgeryFactory.kt | 2 +- ...InternalTelemetryDebugLogForgeryFactory.kt | 2 +- ...InternalTelemetryErrorLogForgeryFactory.kt | 8 +- .../InternalTelemetryMetricForgeryFactory.kt | 2 +- features/dd-sdk-android-rum/api/apiSurface | 67 ----- .../api/dd-sdk-android-rum.api | 233 ------------------ .../generate_telemetry_models.gradle.kts | 3 + .../android/rum/internal/RumFeature.kt | 52 ++-- .../internal/TelemetryEventHandler.kt | 3 - .../android/rum/internal/RumFeatureTest.kt | 47 +--- .../internal/TelemetryEventHandlerTest.kt | 3 +- .../android/core/stub/StubInternalLogger.kt | 2 +- 18 files changed, 64 insertions(+), 472 deletions(-) diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt index 4a043dcbdd..9797bcdd4b 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/_InternalProxy.kt @@ -50,7 +50,7 @@ class _InternalProxy internal constructor( message = message, additionalProperties = null ) - rumFeature?.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) + rumFeature?.sendEvent(telemetryEvent) } fun error(message: String, throwable: Throwable? = null) { @@ -58,7 +58,7 @@ class _InternalProxy internal constructor( message = message, error = throwable ) - rumFeature?.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) + rumFeature?.sendEvent(telemetryEvent) } fun error(message: String, stack: String?, kind: String?) { @@ -67,19 +67,8 @@ class _InternalProxy internal constructor( stacktrace = stack, kind = kind ) - rumFeature?.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) + rumFeature?.sendEvent(telemetryEvent) } - - // region Internal - - private fun bundleEventIntoTelemetry(event: Any): Map { - return mapOf( - "type" to "telemetry_event", - "event" to event - ) - } - - // endregion } @Suppress("PropertyName") diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt index 745fec66a0..e056805021 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt @@ -511,12 +511,7 @@ internal class DatadogCore( batchUploadFrequency = configuration.coreConfig.uploadFrequency.baseStepMs, batchProcessingLevel = configuration.coreConfig.batchProcessingLevel.maxBatchesPerUploadJob ) - rumFeature.sendEvent( - mapOf( - "type" to "telemetry_event", - "event" to event - ) - ) + rumFeature.sendEvent(event) } coreFeature.uploadExecutorService.scheduleSafe( diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt index 2e17f38223..5e83f859a0 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/logger/SdkInternalLogger.kt @@ -104,7 +104,7 @@ internal class SdkInternalLogger( message = messageBuilder(), additionalProperties = additionalProperties ) - rumFeature.sendEvent(bundleEventIntoTelemetry(metricEvent)) + rumFeature.sendEvent(metricEvent) } override fun startPerformanceMeasure( @@ -132,20 +132,13 @@ internal class SdkInternalLogger( ) { if (!RateBasedSampler(samplingRate).sample()) return val rumFeature = sdkCore?.getFeature(Feature.RUM_FEATURE_NAME) ?: return - rumFeature.sendEvent(bundleEventIntoTelemetry(apiUsageEvent)) + rumFeature.sendEvent(apiUsageEvent) } // endregion // region Internal - private fun bundleEventIntoTelemetry(event: Any): Map { - return mapOf( - TYPE_KEY to TELEMETRY_EVENT_MESSAGE_TYPE, - TELEMETRY_EVENT_KEY to event - ) - } - private fun logToUser( level: InternalLogger.Level, messageBuilder: () -> String, @@ -234,7 +227,7 @@ internal class SdkInternalLogger( additionalProperties = additionalProperties ) } - rumFeature.sendEvent(bundleEventIntoTelemetry(telemetryEvent)) + rumFeature.sendEvent(telemetryEvent) } private fun InternalLogger.Level.toLogLevel(): Int { @@ -259,9 +252,6 @@ internal class SdkInternalLogger( companion object { internal const val SDK_LOG_TAG = "DD_LOG" internal const val DEV_LOG_TAG = "Datadog" - private const val TYPE_KEY = "type" - private const val TELEMETRY_EVENT_MESSAGE_TYPE = "telemetry_event" - internal const val TELEMETRY_EVENT_KEY = "event" } // endregion diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt index 37fdd2281f..e98669ffb8 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/InternalProxyTest.kt @@ -52,11 +52,9 @@ internal class InternalProxyTest { proxy._telemetry.debug(message) // Then - argumentCaptor> { + argumentCaptor { verify(mockRumFeatureScope).sendEvent(capture()) - val payload = firstValue - assert(payload["type"] == "telemetry_event") - val logEvent = payload["event"] as InternalTelemetryEvent.Log.Debug + val logEvent = firstValue as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(message) } } @@ -77,11 +75,9 @@ internal class InternalProxyTest { proxy._telemetry.error(message, stack, kind) // Then - argumentCaptor> { + argumentCaptor { verify(mockRumFeatureScope).sendEvent(capture()) - val payload = firstValue - assert(payload["type"] == "telemetry_event") - val logEvent = payload["event"] as InternalTelemetryEvent.Log.Error + val logEvent = firstValue as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(message) assertThat(logEvent.stacktrace).isEqualTo(stack) assertThat(logEvent.kind).isEqualTo(kind) @@ -103,11 +99,9 @@ internal class InternalProxyTest { proxy._telemetry.error(message, throwable) // Then - argumentCaptor> { + argumentCaptor { verify(mockRumFeatureScope).sendEvent(capture()) - val payload = firstValue - assert(payload["type"] == "telemetry_event") - val logEvent = payload["event"] as InternalTelemetryEvent.Log.Error + val logEvent = firstValue as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(message) assertThat(logEvent.error).isEqualTo(throwable) } diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt index 791cd2b8dd..c212da1a34 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/DatadogCoreInitializationTest.kt @@ -373,11 +373,9 @@ internal class DatadogCoreInitializationTest { } testedCore.coreFeature.uploadExecutorService.shutdownNow() - argumentCaptor> { + argumentCaptor { verify(mockRumFeature).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val telemetryConfigurationEvent = firstValue["event"] as InternalTelemetryEvent.Configuration + val telemetryConfigurationEvent = firstValue as InternalTelemetryEvent.Configuration assertThat(telemetryConfigurationEvent.trackErrors) .isEqualTo(configuration.crashReportsEnabled) assertThat(telemetryConfigurationEvent.batchSize) diff --git a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt index 45805cb33f..de4349e503 100644 --- a/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt +++ b/dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/logger/SdkInternalLoggerTest.kt @@ -283,11 +283,9 @@ internal class SdkInternalLoggerTest { ) // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug + val logEvent = firstValue as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isNull() } @@ -318,11 +316,9 @@ internal class SdkInternalLoggerTest { ) // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug + val logEvent = firstValue as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) } @@ -350,11 +346,9 @@ internal class SdkInternalLoggerTest { ) // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug + val logEvent = firstValue as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isEmpty() } @@ -383,11 +377,9 @@ internal class SdkInternalLoggerTest { ) // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Error + val logEvent = firstValue as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) } @@ -417,11 +409,9 @@ internal class SdkInternalLoggerTest { ) // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Error + val logEvent = firstValue as InternalTelemetryEvent.Log.Error assertThat(logEvent.message).isEqualTo(fakeMessage) assertThat(logEvent.error).isEqualTo(fakeThrowable) assertThat(logEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) @@ -452,11 +442,9 @@ internal class SdkInternalLoggerTest { } // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val logEvent = firstValue["event"] as InternalTelemetryEvent.Log.Debug + val logEvent = firstValue as InternalTelemetryEvent.Log.Debug assertThat(logEvent.message).isEqualTo(fakeMessage) } } @@ -481,11 +469,9 @@ internal class SdkInternalLoggerTest { ) // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val metricEvent = firstValue["event"] as InternalTelemetryEvent.Metric + val metricEvent = firstValue as InternalTelemetryEvent.Metric assertThat(metricEvent.message).isEqualTo(fakeMessage) assertThat(metricEvent.additionalProperties).isEqualTo(fakeAdditionalProperties) } @@ -578,11 +564,9 @@ internal class SdkInternalLoggerTest { testedInternalLogger.logApiUsage(fakeApiUsageInternalTelemetryEvent, 100.0f) // Then - argumentCaptor>() { + argumentCaptor() { verify(mockRumFeatureScope).sendEvent(capture()) - assertThat(firstValue.size).isEqualTo(2) - assertThat(firstValue["type"]).isEqualTo("telemetry_event") - val apiUsageEvent = firstValue["event"] as InternalTelemetryEvent.ApiUsage + val apiUsageEvent = firstValue as InternalTelemetryEvent.ApiUsage assertThat(apiUsageEvent).isEqualTo(fakeApiUsageInternalTelemetryEvent) } } diff --git a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt index c3b1db7114..346645aa0a 100644 --- a/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt +++ b/dd-sdk-android-internal/src/testFixtures/kotlin/com/datadog/android/internal/tests/elmyr/InternalTelemetryApiUsageForgeryFactory.kt @@ -17,7 +17,7 @@ class InternalTelemetryApiUsageForgeryFactory : ForgeryFactory? = null, Telemetry? = null) - val type: kotlin.String - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): CommonTelemetryProperties - fun fromJsonObject(com.google.gson.JsonObject): CommonTelemetryProperties - class Dd - val formatVersion: kotlin.Long - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Dd - fun fromJsonObject(com.google.gson.JsonObject): Dd - data class Application - constructor(kotlin.String) - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Application - fun fromJsonObject(com.google.gson.JsonObject): Application - data class Session - constructor(kotlin.String) - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Session - fun fromJsonObject(com.google.gson.JsonObject): Session - data class View - constructor(kotlin.String) - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): View - fun fromJsonObject(com.google.gson.JsonObject): View - data class Action - constructor(kotlin.String) - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Action - fun fromJsonObject(com.google.gson.JsonObject): Action - data class Telemetry - constructor(Device? = null, Os? = null, kotlin.collections.MutableMap = mutableMapOf()) - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Telemetry - fun fromJsonObject(com.google.gson.JsonObject): Telemetry - data class Device - constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null) - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Device - fun fromJsonObject(com.google.gson.JsonObject): Device - data class Os - constructor(kotlin.String? = null, kotlin.String? = null, kotlin.String? = null) - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Os - fun fromJsonObject(com.google.gson.JsonObject): Os - enum Source - constructor(kotlin.String) - - ANDROID - - IOS - - BROWSER - - FLUTTER - - REACT_NATIVE - - UNITY - - KOTLIN_MULTIPLATFORM - fun toJson(): com.google.gson.JsonElement - companion object - fun fromJson(kotlin.String): Source data class com.datadog.android.telemetry.model.TelemetryConfigurationEvent constructor(Dd, kotlin.Long, kotlin.String, Source, kotlin.String, Application? = null, Session? = null, View? = null, Action? = null, kotlin.collections.List? = null, Telemetry) val type: kotlin.String diff --git a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api index 8443542d0b..1942571a28 100644 --- a/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api +++ b/features/dd-sdk-android-rum/api/dd-sdk-android-rum.api @@ -5150,239 +5150,6 @@ public final class com/datadog/android/sqlite/DatadogDatabaseErrorHandler : andr public fun onCorruption (Landroid/database/sqlite/SQLiteDatabase;)V } -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Companion; - public fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;)V - public synthetic fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; - public final fun component10 ()Ljava/util/List; - public final fun component11 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; - public final fun component2 ()J - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; - public final fun component7 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; - public final fun component8 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; - public final fun component9 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; - public final fun copy (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; - public final fun getAction ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; - public final fun getApplication ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; - public final fun getDate ()J - public final fun getDd ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; - public final fun getExperimentalFeatures ()Ljava/util/List; - public final fun getService ()Ljava/lang/String; - public final fun getSession ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; - public final fun getSource ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public final fun getTelemetry ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; - public final fun getType ()Ljava/lang/String; - public final fun getVersion ()Ljava/lang/String; - public final fun getView ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Action { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; - public final fun getId ()Ljava/lang/String; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Action$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Action; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Application { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; - public final fun getId ()Ljava/lang/String; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Application$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Application; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Dd { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd$Companion; - public fun ()V - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; - public final fun getFormatVersion ()J - public final fun toJson ()Lcom/google/gson/JsonElement; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Dd$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Dd; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Device { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; - public final fun getArchitecture ()Ljava/lang/String; - public final fun getBrand ()Ljava/lang/String; - public final fun getModel ()Ljava/lang/String; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Device$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Os { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; - public final fun getBuild ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getVersion ()Ljava/lang/String; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Os$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Session { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; - public final fun getId ()Ljava/lang/String; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Session$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Session; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Source : java/lang/Enum { - public static final field ANDROID Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static final field BROWSER Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source$Companion; - public static final field FLUTTER Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static final field IOS Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static final field KOTLIN_MULTIPLATFORM Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static final field REACT_NATIVE Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static final field UNITY Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public final fun toJson ()Lcom/google/gson/JsonElement; - public static fun valueOf (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; - public static fun values ()[Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Source$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Source; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry$Companion; - public fun ()V - public fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;)V - public synthetic fun (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; - public final fun component2 ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; - public final fun component3 ()Ljava/util/Map; - public final fun copy (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device;Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os;Ljava/util/Map;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; - public final fun getAdditionalProperties ()Ljava/util/Map; - public final fun getDevice ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Device; - public final fun getOs ()Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Os; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$Telemetry; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$View { - public static final field Companion Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View$Companion; - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; - public static synthetic fun copy$default (Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View;Ljava/lang/String;ILjava/lang/Object;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; - public fun equals (Ljava/lang/Object;)Z - public static final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; - public static final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; - public final fun getId ()Ljava/lang/String; - public fun hashCode ()I - public final fun toJson ()Lcom/google/gson/JsonElement; - public fun toString ()Ljava/lang/String; -} - -public final class com/datadog/android/telemetry/model/CommonTelemetryProperties$View$Companion { - public final fun fromJson (Ljava/lang/String;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; - public final fun fromJsonObject (Lcom/google/gson/JsonObject;)Lcom/datadog/android/telemetry/model/CommonTelemetryProperties$View; -} - public final class com/datadog/android/telemetry/model/TelemetryConfigurationEvent { public static final field Companion Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Companion; public fun (Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Dd;JLjava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Source;Ljava/lang/String;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Application;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Session;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$View;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Action;Ljava/util/List;Lcom/datadog/android/telemetry/model/TelemetryConfigurationEvent$Telemetry;)V diff --git a/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts b/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts index 9caaaf4c2c..db2d658af6 100644 --- a/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts +++ b/features/dd-sdk-android-rum/generate_telemetry_models.gradle.kts @@ -15,6 +15,9 @@ tasks.register( ) { inputDirPath = "src/main/json/telemetry" targetPackageName = "com.datadog.android.telemetry.model" + ignoredFiles = arrayOf( + "_common-schema.json" + ) inputNameMapping = mapOf( "debug-schema.json" to "TelemetryDebugEvent", "error-schema.json" to "TelemetryErrorEvent" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 827a2263f9..c23b1a0320 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -248,18 +248,25 @@ internal class RumFeature( // region FeatureEventReceiver override fun onReceive(event: Any) { - if (event is JvmCrash.Rum) { - addJvmCrash(event) - return - } else if (event !is Map<*, *>) { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.USER, - { UNSUPPORTED_EVENT_TYPE.format(Locale.US, event::class.java.canonicalName) } - ) - return + when (event) { + is Map<*, *> -> handleMapLikeEvent(event) + is JvmCrash.Rum -> addJvmCrash(event) + is InternalTelemetryEvent -> handleTelemetryEvent(event) + else -> { + sdkCore.internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { UNSUPPORTED_EVENT_TYPE.format(Locale.US, event::class.java.canonicalName) } + ) + } } + } + + // endregion + // region Internal + + private fun handleMapLikeEvent(event: Map<*, *>) { when (event["type"]) { NDK_CRASH_BUS_MESSAGE_TYPE -> lateCrashEventHandler.handleNdkCrashEvent(event, dataWriter) @@ -269,6 +276,7 @@ internal class RumFeature( WEB_VIEW_INGESTED_NOTIFICATION_MESSAGE_TYPE -> { (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor)?.sendWebViewEvent() } + FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE -> { (GlobalRumMonitor.get(sdkCore) as? DatadogRumMonitor)?.let { it.stopKeepAliveCallback() @@ -276,8 +284,6 @@ internal class RumFeature( } } - TELEMETRY_EVENT_MESSAGE_TYPE -> handleTelemetryEvent(event) - else -> { sdkCore.internalLogger.log( InternalLogger.Level.WARN, @@ -288,22 +294,9 @@ internal class RumFeature( } } - // endregion - - // region Internal - - private fun handleTelemetryEvent(event: Map<*, *>) { - val telemetryEvent = event[EVENT_MESSAGE_KEY] as? InternalTelemetryEvent + private fun handleTelemetryEvent(event: InternalTelemetryEvent) { val advancedRumMonitor = GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor ?: return - if (telemetryEvent != null) { - advancedRumMonitor.sendTelemetryEvent(telemetryEvent) - } else { - sdkCore.internalLogger.log( - InternalLogger.Level.WARN, - InternalLogger.Target.MAINTAINER, - { TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE } - ) - } + advancedRumMonitor.sendTelemetryEvent(event) } @AnyThread @@ -542,7 +535,6 @@ internal class RumFeature( internal const val LOGGER_ERROR_BUS_MESSAGE_TYPE = "logger_error" internal const val LOGGER_ERROR_WITH_STACK_TRACE_MESSAGE_TYPE = "logger_error_with_stacktrace" internal const val WEB_VIEW_INGESTED_NOTIFICATION_MESSAGE_TYPE = "web_view_ingested_notification" - internal const val MOBILE_METRIC_MESSAGE_TYPE = "mobile_metric" internal const val FLUSH_AND_STOP_MONITOR_MESSAGE_TYPE = "flush_and_stop_monitor" internal const val ALL_IN_SAMPLE_RATE: Float = 100f @@ -583,8 +575,6 @@ internal class RumFeature( internal const val EVENT_THROWABLE_PROPERTY = "throwable" internal const val EVENT_ATTRIBUTES_PROPERTY = "attributes" internal const val EVENT_STACKTRACE_PROPERTY = "stacktrace" - internal const val TELEMETRY_EVENT_MESSAGE_TYPE = "telemetry_event" - internal const val EVENT_MESSAGE_KEY = "event" internal const val UNSUPPORTED_EVENT_TYPE = "RUM feature receive an event of unsupported type=%s." @@ -600,8 +590,6 @@ internal class RumFeature( internal const val LOG_ERROR_WITH_STACKTRACE_EVENT_MISSING_MANDATORY_FIELDS = "RUM feature received a log event with stacktrace" + " where mandatory message field is either missing or has a wrong type." - internal const val TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE = "RUM feature received a telemetry" + - " event, but mandatory event field is either missing or has a wrong type." internal const val DEVELOPER_MODE_SAMPLE_RATE_CHANGED_MESSAGE = "Developer mode enabled, setting RUM sample rate to 100%." internal const val RUM_FEATURE_NOT_YET_INITIALIZED = diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 4232a45cff..10ac89f9ac 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -113,8 +113,6 @@ internal class TelemetryEventHandler( trackNetworkRequests = true null } - - else -> null } if (telemetryEvent != null) { writer.write(eventBatchWriter, telemetryEvent, EventType.TELEMETRY) @@ -379,7 +377,6 @@ internal class TelemetryEventHandler( ) ) } - else -> null } } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt index 4e935756a0..9c197ec0a5 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/RumFeatureTest.kt @@ -1202,13 +1202,9 @@ internal class RumFeatureTest { ) { // Given testedFeature.onInitialize(appContext.mockInstance) - val event = mapOf( - "type" to RumFeature.TELEMETRY_EVENT_MESSAGE_TYPE, - "event" to fakeInternalTelemetryEvent - ) // When - testedFeature.onReceive(event) + testedFeature.onReceive(fakeInternalTelemetryEvent) // Then verify(mockRumMonitor).sendTelemetryEvent(fakeInternalTelemetryEvent) @@ -1216,47 +1212,6 @@ internal class RumFeatureTest { verifyNoInteractions(mockInternalLogger) } - @Test - fun `M log warning W onReceive() { telemetry event, event is missing }`() { - // Given - val event = mapOf( - "type" to RumFeature.TELEMETRY_EVENT_MESSAGE_TYPE - ) - - // When - testedFeature.onReceive(event) - - // Then - mockInternalLogger.verifyLog( - InternalLogger.Level.WARN, - InternalLogger.Target.MAINTAINER, - RumFeature.TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE - ) - - verifyNoInteractions(mockRumMonitor) - } - - @Test - fun `M log warning W onReceive() { telemetry event, event is not of type Telemetry }`() { - // Given - val event = mapOf( - "type" to RumFeature.TELEMETRY_EVENT_MESSAGE_TYPE, - "event" to Any() - ) - - // When - testedFeature.onReceive(event) - - // Then - mockInternalLogger.verifyLog( - InternalLogger.Level.WARN, - InternalLogger.Target.MAINTAINER, - RumFeature.TELEMETRY_MISSING_EVENT_FIELD_WARNING_MESSAGE - ) - - verifyNoInteractions(mockRumMonitor) - } - // endregion private fun Forge.anApplicationExitInfoList( diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index 64d33b599d..683e348edc 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -45,7 +45,6 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions @@ -623,7 +622,7 @@ internal class TelemetryEventHandlerTest { val internalTelemetryEvent = forge.anElementFrom( forge.getForgery(), forge.getForgery(), - forge.getForgery(), + forge.getForgery() ) val rawEvent = RumRawEvent.TelemetryEventWrapper(internalTelemetryEvent) val anotherEvent = rawEvent.copy() diff --git a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt index b854ac6529..87737f7ea3 100644 --- a/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt +++ b/reliability/stub-core/src/main/kotlin/com/datadog/android/core/stub/StubInternalLogger.kt @@ -68,7 +68,7 @@ internal class StubInternalLogger : InternalLogger { apiUsageEvent: InternalTelemetryEvent.ApiUsage, samplingRate: Float ) { - println("AU [T]: ${apiUsageEvent::class.simpleName} | $samplingRate%") + println("${apiUsageEvent::class.simpleName} | $samplingRate%") apiUsageEvent.additionalProperties.log() } From 263dac87e6d1385a97b877402daa151a2b2bcb75 Mon Sep 17 00:00:00 2001 From: luyi Date: Mon, 9 Sep 2024 17:56:58 +0200 Subject: [PATCH 028/111] RUM-5184: Add dynamic optimization field in SessionReplayConfiguration --- .../api/apiSurface | 1 + .../api/dd-sdk-android-session-replay.api | 5 ++- .../android/sessionreplay/SessionReplay.kt | 3 +- .../SessionReplayConfiguration.kt | 16 +++++++- .../internal/DefaultRecorderProvider.kt | 6 ++- .../internal/SessionReplayFeature.kt | 6 ++- .../internal/recorder/Debouncer.kt | 11 ++++-- .../recorder/DefaultOnDrawListenerProducer.kt | 6 ++- .../recorder/SessionReplayRecorder.kt | 6 ++- .../listener/WindowsOnDrawListener.kt | 6 ++- .../SessionReplayConfigurationBuilderTest.kt | 16 ++++++++ ...essionReplayConfigurationForgeryFactory.kt | 3 +- .../internal/SessionReplayFeatureTest.kt | 6 ++- .../internal/recorder/DebouncerTest.kt | 38 ++++++++++++++++++- .../listener/WindowsOnDrawListenerTest.kt | 11 +++++- 15 files changed, 116 insertions(+), 24 deletions(-) diff --git a/features/dd-sdk-android-session-replay/api/apiSurface b/features/dd-sdk-android-session-replay/api/apiSurface index dae425c488..d009b38d39 100644 --- a/features/dd-sdk-android-session-replay/api/apiSurface +++ b/features/dd-sdk-android-session-replay/api/apiSurface @@ -23,6 +23,7 @@ data class com.datadog.android.sessionreplay.SessionReplayConfiguration fun setTouchPrivacy(TouchPrivacy): Builder fun startRecordingImmediately(Boolean): Builder fun setTextAndInputPrivacy(TextAndInputPrivacy): Builder + fun setDynamicOptimizationEnabled(Boolean): Builder fun build(): SessionReplayConfiguration enum com.datadog.android.sessionreplay.SessionReplayPrivacy - ALLOW diff --git a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api index 5746219517..a3f3194e2f 100644 --- a/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api +++ b/features/dd-sdk-android-session-replay/api/dd-sdk-android-session-replay.api @@ -34,8 +34,8 @@ public final class com/datadog/android/sessionreplay/SessionReplay { } public final class com/datadog/android/sessionreplay/SessionReplayConfiguration { - public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; - public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;ILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public final fun copy (Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public static synthetic fun copy$default (Lcom/datadog/android/sessionreplay/SessionReplayConfiguration;Ljava/lang/String;Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;Ljava/util/List;Ljava/util/List;FLcom/datadog/android/sessionreplay/ImagePrivacy;ZLcom/datadog/android/sessionreplay/TouchPrivacy;Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;ZILjava/lang/Object;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -45,6 +45,7 @@ public final class com/datadog/android/sessionreplay/SessionReplayConfiguration$ public fun (F)V public final fun addExtensionSupport (Lcom/datadog/android/sessionreplay/ExtensionSupport;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun build ()Lcom/datadog/android/sessionreplay/SessionReplayConfiguration; + public final fun setDynamicOptimizationEnabled (Z)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun setImagePrivacy (Lcom/datadog/android/sessionreplay/ImagePrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun setPrivacy (Lcom/datadog/android/sessionreplay/SessionReplayPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; public final fun setTextAndInputPrivacy (Lcom/datadog/android/sessionreplay/TextAndInputPrivacy;)Lcom/datadog/android/sessionreplay/SessionReplayConfiguration$Builder; diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt index 058bf647ee..9e731ebbe9 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplay.kt @@ -40,7 +40,8 @@ object SessionReplay { customMappers = sessionReplayConfiguration.customMappers, customOptionSelectorDetectors = sessionReplayConfiguration.customOptionSelectorDetectors, sampleRate = sessionReplayConfiguration.sampleRate, - startRecordingImmediately = sessionReplayConfiguration.startRecordingImmediately + startRecordingImmediately = sessionReplayConfiguration.startRecordingImmediately, + dynamicOptimizationEnabled = sessionReplayConfiguration.dynamicOptimizationEnabled ) sdkCore.registerFeature(sessionReplayFeature) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt index ad790cde2b..e6de1faa1a 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/SessionReplayConfiguration.kt @@ -22,7 +22,8 @@ data class SessionReplayConfiguration internal constructor( internal val imagePrivacy: ImagePrivacy, internal val startRecordingImmediately: Boolean, internal val touchPrivacy: TouchPrivacy, - internal val textAndInputPrivacy: TextAndInputPrivacy + internal val textAndInputPrivacy: TextAndInputPrivacy, + internal val dynamicOptimizationEnabled: Boolean ) { /** @@ -42,6 +43,7 @@ data class SessionReplayConfiguration internal constructor( private var touchPrivacy = TouchPrivacy.HIDE private var textAndInputPrivacy = TextAndInputPrivacy.MASK_ALL private var extensionSupport: ExtensionSupport = NoOpExtensionSupport() + private var dynamicOptimizationEnabled = true /** * Adds an extension support implementation. This is mostly used when you want to provide @@ -149,6 +151,15 @@ data class SessionReplayConfiguration internal constructor( return this } + /** + * This option controls whether optimization is enabled or disabled for recording Session Replay data. + * By default the value is true, meaning the dynamic optimization is enabled. + */ + fun setDynamicOptimizationEnabled(dynamicOptimizationEnabled: Boolean): Builder { + this.dynamicOptimizationEnabled = dynamicOptimizationEnabled + return this + } + /** * Builds a [SessionReplayConfiguration] based on the current state of this Builder. */ @@ -162,7 +173,8 @@ data class SessionReplayConfiguration internal constructor( customMappers = customMappers(), customOptionSelectorDetectors = extensionSupport.getOptionSelectorDetectors(), sampleRate = sampleRate, - startRecordingImmediately = startRecordingImmediately + startRecordingImmediately = startRecordingImmediately, + dynamicOptimizationEnabled = dynamicOptimizationEnabled ) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt index d4384856ac..cdac483ade 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/DefaultRecorderProvider.kt @@ -62,7 +62,8 @@ internal class DefaultRecorderProvider( private val imagePrivacy: ImagePrivacy, private val touchPrivacy: TouchPrivacy, private val customMappers: List>, - private val customOptionSelectorDetectors: List + private val customOptionSelectorDetectors: List, + private val dynamicOptimizationEnabled: Boolean ) : RecorderProvider { override fun provideSessionReplayRecorder( @@ -83,7 +84,8 @@ internal class DefaultRecorderProvider( timeProvider = SessionReplayTimeProvider(sdkCore), mappers = customMappers + builtInMappers(), customOptionSelectorDetectors = customOptionSelectorDetectors, - sdkCore = sdkCore + sdkCore = sdkCore, + dynamicOptimizationEnabled = dynamicOptimizationEnabled ) } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt index 1b28872329..96978f9926 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/SessionReplayFeature.kt @@ -66,7 +66,8 @@ internal class SessionReplayFeature( customMappers: List>, customOptionSelectorDetectors: List, sampleRate: Float, - startRecordingImmediately: Boolean + startRecordingImmediately: Boolean, + dynamicOptimizationEnabled: Boolean ) : this( sdkCore, customEndpointUrl, @@ -82,7 +83,8 @@ internal class SessionReplayFeature( imagePrivacy, touchPrivacy, customMappers, - customOptionSelectorDetectors + customOptionSelectorDetectors, + dynamicOptimizationEnabled ) ) diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt index 24b01f63f1..f2cab2e730 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/Debouncer.kt @@ -16,7 +16,8 @@ internal class Debouncer( private val handler: Handler = Handler(Looper.getMainLooper()), private val maxRecordDelayInNs: Long = MAX_DELAY_THRESHOLD_NS, private val timeBank: TimeBank = RecordingTimeBank(), - private val sdkCore: FeatureSdkCore + private val sdkCore: FeatureSdkCore, + private val dynamicOptimizationEnabled: Boolean ) { private var lastTimeRecordWasPerformed = 0L @@ -41,10 +42,14 @@ internal class Debouncer( } private fun executeRunnable(runnable: Runnable) { - runInTimeBalance { + if (dynamicOptimizationEnabled) { + runInTimeBalance { + runnable.run() + } + } else { runnable.run() - lastTimeRecordWasPerformed = System.nanoTime() } + lastTimeRecordWasPerformed = System.nanoTime() } private fun runInTimeBalance(block: () -> Unit) { diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt index defddf5b78..94d4e88922 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/DefaultOnDrawListenerProducer.kt @@ -18,7 +18,8 @@ import com.datadog.android.sessionreplay.internal.recorder.listener.WindowsOnDra internal class DefaultOnDrawListenerProducer( private val snapshotProducer: SnapshotProducer, private val recordedDataQueueHandler: RecordedDataQueueHandler, - private val sdkCore: FeatureSdkCore + private val sdkCore: FeatureSdkCore, + private val dynamicOptimizationEnabled: Boolean ) : OnDrawListenerProducer { override fun create( @@ -33,7 +34,8 @@ internal class DefaultOnDrawListenerProducer( textAndInputPrivacy = textAndInputPrivacy, imagePrivacy = imagePrivacy, sdkCore = sdkCore, - methodCallSamplingRate = MethodCallSamplingRate.LOW.rate + methodCallSamplingRate = MethodCallSamplingRate.LOW.rate, + dynamicOptimizationEnabled = dynamicOptimizationEnabled ) } } diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt index b3dd38acd9..522c3fa371 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/SessionReplayRecorder.kt @@ -86,7 +86,8 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { customOptionSelectorDetectors: List = emptyList(), windowInspector: WindowInspector = WindowInspector, sdkCore: FeatureSdkCore, - resourceDataStoreManager: ResourceDataStoreManager + resourceDataStoreManager: ResourceDataStoreManager, + dynamicOptimizationEnabled: Boolean ) { val internalLogger = sdkCore.internalLogger val rumContextDataHandler = RumContextDataHandler( @@ -180,7 +181,8 @@ internal class SessionReplayRecorder : OnWindowRefreshedCallback, Recorder { ) ), recordedDataQueueHandler = recordedDataQueueHandler, - sdkCore = sdkCore + sdkCore = sdkCore, + dynamicOptimizationEnabled = dynamicOptimizationEnabled ) ) this.windowCallbackInterceptor = WindowCallbackInterceptor( diff --git a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt index a87c0c7073..ea36bc39cb 100644 --- a/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt +++ b/features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/listener/WindowsOnDrawListener.kt @@ -30,7 +30,11 @@ internal class WindowsOnDrawListener( private val imagePrivacy: ImagePrivacy, private val miscUtils: MiscUtils = MiscUtils, private val sdkCore: FeatureSdkCore, - private val debouncer: Debouncer = Debouncer(sdkCore = sdkCore), + dynamicOptimizationEnabled: Boolean, + private val debouncer: Debouncer = Debouncer( + sdkCore = sdkCore, + dynamicOptimizationEnabled = dynamicOptimizationEnabled + ), private val methodCallSamplingRate: Float ) : ViewTreeObserver.OnDrawListener { diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt index 36ec5e0deb..e6ab76ff75 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/SessionReplayConfigurationBuilderTest.kt @@ -8,6 +8,7 @@ package com.datadog.android.sessionreplay import android.view.View import com.datadog.android.sessionreplay.forge.ForgeConfigurator +import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.StringForgery @@ -62,6 +63,7 @@ internal class SessionReplayConfigurationBuilderTest { assertThat(sessionReplayConfiguration.customMappers).isEmpty() assertThat(sessionReplayConfiguration.customOptionSelectorDetectors).isEmpty() assertThat(sessionReplayConfiguration.sampleRate).isEqualTo(fakeSampleRate) + assertThat(sessionReplayConfiguration.dynamicOptimizationEnabled).isEqualTo(true) } @Test @@ -146,6 +148,20 @@ internal class SessionReplayConfigurationBuilderTest { .isEqualTo(fakeExpectedCustomMappers) } + @Test + fun `M use the given dynamic optimization W setDynamicOptimization()`( + @BoolForgery fakeDynamicOptimizationEnabled: Boolean + ) { + // Given + val sessionReplayConfiguration = testedBuilder + .setDynamicOptimizationEnabled(fakeDynamicOptimizationEnabled) + .build() + + // Then + assertThat(sessionReplayConfiguration.dynamicOptimizationEnabled) + .isEqualTo(fakeDynamicOptimizationEnabled) + } + @Test fun `M return empty map W addExtensionSupport { no mappers provided }`() { // Given diff --git a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt index 4e889fbba4..2e2a5c7401 100644 --- a/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt +++ b/features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/forge/SessionReplayConfigurationForgeryFactory.kt @@ -26,7 +26,8 @@ class SessionReplayConfigurationForgeryFactory : ForgeryFactory Date: Mon, 16 Sep 2024 14:32:43 +0200 Subject: [PATCH 029/111] Handle TelemetryUsageEvent in RumEventMapper --- .../internal/domain/event/RumEventMapper.kt | 2 + .../domain/event/RumEventMapperTest.kt | 13 ++++ .../android/rum/utils/forge/Configurator.kt | 1 + .../TelemetryUsageEventForgeryFactory.kt | 67 +++++++++++++++++++ .../internal/TelemetryEventHandlerTest.kt | 16 +++-- 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryUsageEventForgeryFactory.kt diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt index 20d66a2920..00c664caf0 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapper.kt @@ -17,6 +17,7 @@ import com.datadog.android.rum.model.ViewEvent import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import java.util.Locale internal data class RumEventMapper( @@ -61,6 +62,7 @@ internal data class RumEventMapper( is LongTaskEvent -> longTaskEventMapper.map(event) is TelemetryConfigurationEvent -> telemetryConfigurationMapper.map(event) is TelemetryDebugEvent, + is TelemetryUsageEvent, is TelemetryErrorEvent -> event else -> { internalLogger.log( diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt index 612c76621d..1ae60d49a8 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventMapperTest.kt @@ -19,6 +19,7 @@ import com.datadog.android.rum.utils.verifyLog import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -208,6 +209,18 @@ internal class RumEventMapperTest { assertThat(mappedRumEvent).isEqualTo(fakeRumEvent) } + @Test + fun `M return the original event W map { TelemetryUsageEvent }`( + @Forgery telemetryUsageEvent: TelemetryUsageEvent + ) { + // WHEN + val mappedRumEvent = testedRumEventMapper.map(telemetryUsageEvent) + + // THEN + verifyNoInteractions(mockInternalLogger) + assertThat(mappedRumEvent).isSameAs(telemetryUsageEvent) + } + @Test fun `M return the original event W map { TelemetryDebugEvent }`( @Forgery telemetryDebugEvent: TelemetryDebugEvent diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt index a94c70b747..e92da37b22 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/Configurator.kt @@ -50,6 +50,7 @@ internal class Configurator : BaseConfigurator() { forge.addFactory(TelemetryDebugEventForgeryFactory()) forge.addFactory(TelemetryErrorEventForgeryFactory()) forge.addFactory(TelemetryConfigurationEventForgeryFactory()) + forge.addFactory(TelemetryUsageEventForgeryFactory()) // Telemetry internal models forge.addFactory(InternalTelemetryEventForgeryFactory()) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryUsageEventForgeryFactory.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryUsageEventForgeryFactory.kt new file mode 100644 index 0000000000..8870c0e106 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/forge/TelemetryUsageEventForgeryFactory.kt @@ -0,0 +1,67 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.utils.forge + +import com.datadog.android.telemetry.model.TelemetryUsageEvent +import com.datadog.android.tests.elmyr.exhaustiveAttributes +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class TelemetryUsageEventForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): TelemetryUsageEvent { + return TelemetryUsageEvent( + dd = TelemetryUsageEvent.Dd(), + date = forge.aPositiveLong(), + service = forge.anAlphabeticalString(), + source = forge.aValueFrom(TelemetryUsageEvent.Source::class.java), + version = forge.anAlphabeticalString(), + application = forge.aNullable { + TelemetryUsageEvent.Application( + id = anAlphabeticalString() + ) + }, + session = forge.aNullable { + TelemetryUsageEvent.Session( + id = anAlphabeticalString() + ) + }, + view = forge.aNullable { + TelemetryUsageEvent.View( + id = anAlphabeticalString() + ) + }, + action = forge.aNullable { + TelemetryUsageEvent.Action( + id = anAlphabeticalString() + ) + }, + experimentalFeatures = forge.aNullable { aList { anAlphabeticalString() } }, + telemetry = TelemetryUsageEvent.Telemetry( + device = forge.aNullable { + TelemetryUsageEvent.Device( + architecture = anAlphabeticalString(), + brand = anAlphabeticalString(), + model = anAlphabeticalString() + ) + }, + os = forge.aNullable { + TelemetryUsageEvent.Os( + build = anAlphabeticalString(), + name = anAlphabeticalString(), + version = anAlphabeticalString() + ) + }, + usage = TelemetryUsageEvent.Usage.AddViewLoadingTime( + noView = forge.aBool(), + noActiveView = forge.aBool(), + overwritten = forge.aBool() + ), + additionalProperties = forge.exhaustiveAttributes() + ) + ) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index 683e348edc..a0db206c4e 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -557,10 +557,9 @@ internal class TelemetryEventHandlerTest { // region Sampling @Test - fun `M not write event W handleEvent() { event is not sampled }`( - @Forgery fakeInternalTelemetryEvent: InternalTelemetryEvent - ) { + fun `M not write event W handleEvent() { event is not sampled }`(forge: Forge) { // Given + val fakeInternalTelemetryEvent = forge.forgeWritableInternalTelemetryEvent() val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeInternalTelemetryEvent) whenever(mockSampler.sample()) doReturn false @@ -772,7 +771,7 @@ internal class TelemetryEventHandlerTest { val events = forge.aList( size = MAX_EVENTS_PER_SESSION_TEST * 10 - ) { forge.getForgery() } + ) { forge.forgeWritableInternalTelemetryEvent() } // remove unwanted identity collisions .groupBy { it.identity } .map { RumRawEvent.TelemetryEventWrapper(it.value.first()) } @@ -914,6 +913,15 @@ internal class TelemetryEventHandlerTest { .hasUseLocalEncryption(internalConfigurationEvent.useLocalEncryption) } + private fun Forge.forgeWritableInternalTelemetryEvent(): InternalTelemetryEvent { + return anElementFrom( + getForgery(), + getForgery(), + getForgery(), + getForgery() + ) + } + // endregion companion object { From c820a409340887fe0741e4722ee746b73a4525a1 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Tue, 17 Sep 2024 09:29:30 +0200 Subject: [PATCH 030/111] Handle the TelemetryUsageEvent sampling correctly --- .../android/rum/internal/RumFeature.kt | 2 +- .../rum/internal/domain/scope/RumViewScope.kt | 9 + .../internal/TelemetryEventHandler.kt | 10 +- .../assertj/TelemetryUsageEventAssert.kt | 217 ++++++++++++++++++ .../assertj/ViewLoadingTimeEventAssert.kt | 50 ++++ .../internal/TelemetryEventHandlerTest.kt | 105 ++++++++- 6 files changed, 386 insertions(+), 7 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/ViewLoadingTimeEventAssert.kt diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index c23b1a0320..0e64b9c093 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -540,7 +540,7 @@ internal class RumFeature( internal const val ALL_IN_SAMPLE_RATE: Float = 100f internal const val DEFAULT_SAMPLE_RATE: Float = 100f internal const val DEFAULT_TELEMETRY_SAMPLE_RATE: Float = 20f - internal const val DEFAULT_TELEMETRY_CONFIGURATION_SAMPLE_RATE: Float = 20f + internal const val DEFAULT_TELEMETRY_CONFIGURATION_SAMPLE_RATE: Float = 100f internal const val DEFAULT_LONG_TASK_THRESHOLD_MS = 100L internal const val DD_TELEMETRY_CONFIG_SAMPLE_RATE_TAG = "_dd.telemetry.configuration_sample_rate" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 8834bbf3b2..88624605d9 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -15,6 +15,7 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.utils.loggableStackTrace +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumAttributes @@ -242,6 +243,14 @@ internal open class RumViewScope( private fun onAddViewLoadingTime(event: RumRawEvent.AddViewLoadingTime, writer: DataWriter) { if (stopped) return val canAddViewLoadingTime = event.overwrite || viewLoadingTime == null + sdkCore.internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = event.overwrite, + noView = viewId.isEmpty(), + noActiveView = !isActive() + ), + 100.0f + ) if (canAddViewLoadingTime) { viewLoadingTime = event.eventTime.nanoTime - startedNanos sendViewUpdate(event, writer) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 10ac89f9ac..6db598a351 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -137,7 +137,7 @@ internal class TelemetryEventHandler( val eventIdentity = event.identity - if (event !is InternalTelemetryEvent.Metric && eventIDsSeenInCurrentSession.contains(eventIdentity)) { + if (isLog(event) && eventIDsSeenInCurrentSession.contains(eventIdentity)) { sdkCore.internalLogger.log( InternalLogger.Level.INFO, InternalLogger.Target.MAINTAINER, @@ -158,6 +158,10 @@ internal class TelemetryEventHandler( return true } + private fun isLog(event: InternalTelemetryEvent): Boolean { + return event is InternalTelemetryEvent.Log + } + private fun createDebugEvent( datadogContext: DatadogContext, timestamp: Long, @@ -351,7 +355,7 @@ internal class TelemetryEventHandler( datadogContext.source, sdkCore.internalLogger ) ?: TelemetryUsageEvent.Source.ANDROID, - service = datadogContext.service, + service = TELEMETRY_SERVICE_NAME, version = datadogContext.sdkVersion, application = TelemetryUsageEvent.Application(rumContext.applicationId), session = TelemetryUsageEvent.Session(rumContext.sessionId), @@ -442,7 +446,7 @@ internal class TelemetryEventHandler( companion object { const val MAX_EVENTS_PER_SESSION = 100 - const val DEFAULT_CONFIGURATION_SAMPLE_RATE = 20f + const val DEFAULT_CONFIGURATION_SAMPLE_RATE = 100f const val ALREADY_SEEN_EVENT_MESSAGE = "Already seen telemetry event with identity=%s, rejecting." const val MAX_EVENT_NUMBER_REACHED_MESSAGE = diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt new file mode 100644 index 0000000000..2aece50e4b --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/TelemetryUsageEventAssert.kt @@ -0,0 +1,217 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.telemetry.assertj + +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent +import org.assertj.core.api.AbstractObjectAssert +import org.assertj.core.api.Assertions.assertThat + +internal class TelemetryUsageEventAssert(actual: TelemetryUsageEvent) : + AbstractObjectAssert( + actual, + TelemetryUsageEventAssert::class.java + ) { + + fun hasDate(expected: Long): TelemetryUsageEventAssert { + assertThat(actual.date) + .overridingErrorMessage( + "Expected event data to have date $expected but was ${actual.date}" + ) + .isEqualTo(expected) + return this + } + + fun hasSource(expected: TelemetryUsageEvent.Source): TelemetryUsageEventAssert { + assertThat(actual.source) + .overridingErrorMessage( + "Expected event data to have source $expected but was ${actual.source}" + ) + .isEqualTo(expected) + return this + } + + fun hasService(expected: String): TelemetryUsageEventAssert { + assertThat(actual.service) + .overridingErrorMessage( + "Expected event data to have service $expected but was ${actual.service}" + ) + .isEqualTo(expected) + return this + } + + fun hasVersion(expected: String): TelemetryUsageEventAssert { + assertThat(actual.version) + .overridingErrorMessage( + "Expected event data to have version $expected but was ${actual.version}" + ) + .isEqualTo(expected) + return this + } + + fun hasApplicationId(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.application?.id) + .overridingErrorMessage( + "Expected event data to have" + + " application.id $expected but was ${actual.application?.id}" + ) + .isEqualTo(expected) + return this + } + + fun hasSessionId(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.session?.id) + .overridingErrorMessage( + "Expected event data to have session.id $expected but was ${actual.session?.id}" + ) + .isEqualTo(expected) + return this + } + + fun hasViewId(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.view?.id) + .overridingErrorMessage( + "Expected event data to have view.id $expected but was ${actual.view?.id}" + ) + .isEqualTo(expected) + return this + } + + fun hasActionId(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.action?.id) + .overridingErrorMessage( + "Expected event data to have action.id $expected but was ${actual.action?.id}" + ) + .isEqualTo(expected) + return this + } + + fun hasAdditionalProperties(additionalProperties: Map): TelemetryUsageEventAssert { + assertThat(actual.telemetry.additionalProperties) + .overridingErrorMessage( + "Expected event data to have telemetry.additionalProperties $additionalProperties" + + " but was ${actual.telemetry.additionalProperties}" + ) + .isEqualTo(additionalProperties) + return this + } + + fun hasDeviceArchitecture(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.telemetry.device?.architecture) + .overridingErrorMessage( + "Expected event data to have telemetry.device architecture $expected" + + " but was ${actual.telemetry.device?.architecture}" + ) + .isEqualTo(expected) + return this + } + + fun hasDeviceModel(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.telemetry.device?.model) + .overridingErrorMessage( + "Expected event data to have telemetry.device model $expected" + + " but was ${actual.telemetry.device?.model}" + ) + .isEqualTo(expected) + return this + } + + fun hasDeviceBrand(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.telemetry.device?.brand) + .overridingErrorMessage( + "Expected event data to have telemetry.device brand $expected" + + " but was ${actual.telemetry.device?.brand}" + ) + .isEqualTo(expected) + return this + } + + fun hasOsBuild(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.telemetry.os?.build) + .overridingErrorMessage( + "Expected event data to have telemetry.os build $expected" + + " but was ${actual.telemetry.os?.build}" + ) + .isEqualTo(expected) + return this + } + + fun hasOsName(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.telemetry.os?.name) + .overridingErrorMessage( + "Expected event data to have telemetry.os name $expected" + + " but was ${actual.telemetry.os?.name}" + ) + .isEqualTo(expected) + return this + } + + fun hasOsVersion(expected: String?): TelemetryUsageEventAssert { + assertThat(actual.telemetry.os?.version) + .overridingErrorMessage( + "Expected event data to have telemetry.os version $expected" + + " but was ${actual.telemetry.os?.version}" + ) + .isEqualTo(expected) + return this + } + + fun hasUsage(expected: InternalTelemetryEvent.ApiUsage) { + when (expected) { + is InternalTelemetryEvent.ApiUsage.AddViewLoadingTime -> { + val actualUsage = actual.telemetry.usage as TelemetryUsageEvent.Usage.AddViewLoadingTime + assertThat(actualUsage) + .hasNoView(expected.noView) + .hasOverwritten(actualUsage.overwritten) + .hasNoActiveView(expected.noActiveView) + } + } + } + + private class ViewLoadingTimeEventAssert(actual: TelemetryUsageEvent.Usage.AddViewLoadingTime) : + AbstractObjectAssert( + actual, + ViewLoadingTimeEventAssert::class.java + ) { + + fun hasNoView(expected: Boolean): ViewLoadingTimeEventAssert { + assertThat(actual.noView) + .overridingErrorMessage( + "Expected viewLoadingTimeUsage event to" + + " have noView $expected but was ${actual.noView}" + ) + .isEqualTo(expected) + return this + } + + fun hasNoActiveView(expected: Boolean): ViewLoadingTimeEventAssert { + assertThat(actual.noActiveView) + .overridingErrorMessage( + "Expected viewLoadingTimeUsage event to have" + + " noActiveView $expected but was ${actual.noActiveView}" + ) + .isEqualTo(expected) + return this + } + + fun hasOverwritten(expected: Boolean): ViewLoadingTimeEventAssert { + assertThat(actual.overwritten) + .overridingErrorMessage( + "Expected viewLoadingTimeUsage event to have" + + " overwritten $expected but was ${actual.overwritten}" + ) + .isEqualTo(expected) + return this + } + } + + companion object { + fun assertThat(actual: TelemetryUsageEvent) = TelemetryUsageEventAssert(actual) + private fun assertThat(actual: TelemetryUsageEvent.Usage.AddViewLoadingTime) = + ViewLoadingTimeEventAssert(actual) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/ViewLoadingTimeEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/ViewLoadingTimeEventAssert.kt new file mode 100644 index 0000000000..cadbd588a7 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/assertj/ViewLoadingTimeEventAssert.kt @@ -0,0 +1,50 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.telemetry.assertj + +import com.datadog.android.telemetry.model.TelemetryUsageEvent +import org.assertj.core.api.AbstractObjectAssert +import org.assertj.core.api.Assertions.assertThat + +internal class ViewLoadingTimeEventAssert(actual: TelemetryUsageEvent.Usage.AddViewLoadingTime) : + AbstractObjectAssert( + actual, + ViewLoadingTimeEventAssert::class.java + ) { + + fun hasNoView(expected: Boolean): ViewLoadingTimeEventAssert { + assertThat(actual.noView) + .overridingErrorMessage( + "Expected event data to have noView $expected but was ${actual.noView}" + ) + .isEqualTo(expected) + return this + } + + fun hasNoActiveView(expected: Boolean): ViewLoadingTimeEventAssert { + assertThat(actual.noActiveView) + .overridingErrorMessage( + "Expected event data to have noActiveView $expected but was ${actual.noActiveView}" + ) + .isEqualTo(expected) + return this + } + + fun hasOverwritten(expected: Boolean): ViewLoadingTimeEventAssert { + assertThat(actual.overwritten) + .overridingErrorMessage( + "Expected event data to have overwriten $expected but was ${actual.overwritten}" + ) + .isEqualTo(expected) + return this + } + + companion object { + fun assertThat(actual: TelemetryUsageEvent.Usage.AddViewLoadingTime) = + ViewLoadingTimeEventAssert(actual) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index a0db206c4e..ee88da6010 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -30,9 +30,11 @@ import com.datadog.android.rum.utils.verifyLog import com.datadog.android.telemetry.assertj.TelemetryConfigurationEventAssert.Companion.assertThat import com.datadog.android.telemetry.assertj.TelemetryDebugEventAssert.Companion.assertThat import com.datadog.android.telemetry.assertj.TelemetryErrorEventAssert.Companion.assertThat +import com.datadog.android.telemetry.assertj.TelemetryUsageEventAssert.Companion.assertThat import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import com.datadog.tools.unit.setStaticValue import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery @@ -614,14 +616,13 @@ internal class TelemetryEventHandlerTest { } @Test - fun `M not write event W handleEvent(){ seen in the session, not metric }`( + fun `M not write event W handleEvent(){ seen in the session, log event }`( forge: Forge ) { // Given val internalTelemetryEvent = forge.anElementFrom( forge.getForgery(), - forge.getForgery(), - forge.getForgery() + forge.getForgery() ) val rawEvent = RumRawEvent.TelemetryEventWrapper(internalTelemetryEvent) val anotherEvent = rawEvent.copy() @@ -792,9 +793,107 @@ internal class TelemetryEventHandlerTest { } // endregion +// region Api Usage + + @Test + fun `M create api usage event W handleEvent(api usage event)`( + @Forgery fakeApiUsageEvent: InternalTelemetryEvent.ApiUsage + ) { + // Given + val fakeWrappedEvent = RumRawEvent.TelemetryEventWrapper(fakeApiUsageEvent) + + // When + testedTelemetryHandler.handleEvent(fakeWrappedEvent, mockWriter) + + // Then + argumentCaptor { + verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) + assertApiUsageMatchesInternalEvent( + lastValue, + fakeApiUsageEvent, + fakeRumContext, + fakeWrappedEvent.eventTime.timestamp + ) + } + } + + @Test + fun `M write event W handleEvent(){ seen in the session, is api usage }`( + @Forgery fakeApiUsageEvent: InternalTelemetryEvent.ApiUsage + ) { + // Given + val rawEvent = RumRawEvent.TelemetryEventWrapper(fakeApiUsageEvent) + val events = listOf(rawEvent, RumRawEvent.TelemetryEventWrapper(fakeApiUsageEvent)) + + // When + testedTelemetryHandler.handleEvent(events[0], mockWriter) + testedTelemetryHandler.handleEvent(events[1], mockWriter) + + // Then + argumentCaptor { + verify(mockWriter, times(2)).write(eq(mockEventBatchWriter), capture(), eq(EventType.TELEMETRY)) + assertApiUsageMatchesInternalEvent( + firstValue as TelemetryUsageEvent, + fakeApiUsageEvent, + fakeRumContext, + rawEvent.eventTime.timestamp + ) + assertApiUsageMatchesInternalEvent( + secondValue as TelemetryUsageEvent, + fakeApiUsageEvent, + fakeRumContext, + rawEvent.eventTime.timestamp + ) + } + } + + @Test + fun `M not write events over the limit W handleEvent() { api usage event }`( + forge: Forge + ) { + val events = (0..MAX_EVENTS_PER_SESSION_TEST).map { forge.getForgery() } + + // When + events.forEach { + testedTelemetryHandler.handleEvent(RumRawEvent.TelemetryEventWrapper(it), mockWriter) + } + + // Then + mockInternalLogger.verifyLog( + level = InternalLogger.Level.INFO, + target = InternalLogger.Target.MAINTAINER, + message = TelemetryEventHandler.MAX_EVENT_NUMBER_REACHED_MESSAGE + ) + } +// endregion // region Assertions + private fun assertApiUsageMatchesInternalEvent( + actual: TelemetryUsageEvent, + internalUsageEvent: InternalTelemetryEvent.ApiUsage, + rumContext: RumContext, + time: Long + ) { + assertThat(actual) + .hasDate(time + fakeServerOffset) + .hasSource(TelemetryUsageEvent.Source.ANDROID) + .hasService(TelemetryEventHandler.TELEMETRY_SERVICE_NAME) + .hasVersion(fakeDatadogContext.sdkVersion) + .hasApplicationId(rumContext.applicationId) + .hasSessionId(rumContext.sessionId) + .hasViewId(rumContext.viewId) + .hasActionId(rumContext.actionId) + .hasAdditionalProperties(internalUsageEvent.additionalProperties ?: emptyMap()) + .hasDeviceArchitecture(fakeDeviceArchitecture) + .hasDeviceBrand(fakeDeviceBrand) + .hasDeviceModel(fakeDeviceModel) + .hasOsBuild(fakeOsBuildId) + .hasOsName(fakeOsName) + .hasOsVersion(fakeOsVersion) + .hasUsage(internalUsageEvent) + } + private fun assertDebugEventMatchesInternalEvent( actual: TelemetryDebugEvent, internalDebugEvent: InternalTelemetryEvent.Log.Debug, From ddab0b1b3650a4c549925587e88195e0598d0879 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:55:23 +0300 Subject: [PATCH 031/111] RUM-6136: Update android e2e for fgm --- .../android/sample/SampleApplication.kt | 197 +++++++++++------- 1 file changed, 124 insertions(+), 73 deletions(-) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt index 23415ed7a7..71427a12dc 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/SampleApplication.kt @@ -19,7 +19,6 @@ import com.datadog.android.core.configuration.BatchSize import com.datadog.android.core.configuration.Configuration import com.datadog.android.core.configuration.UploadFrequency import com.datadog.android.core.sampling.RateBasedSampler -import com.datadog.android.event.EventMapper import com.datadog.android.log.Logger import com.datadog.android.log.Logs import com.datadog.android.log.LogsConfiguration @@ -31,12 +30,6 @@ import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.Rum import com.datadog.android.rum.RumConfiguration import com.datadog.android.rum.RumErrorSource -import com.datadog.android.rum.event.ViewEventMapper -import com.datadog.android.rum.model.ActionEvent -import com.datadog.android.rum.model.ErrorEvent -import com.datadog.android.rum.model.LongTaskEvent -import com.datadog.android.rum.model.ResourceEvent -import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.tracking.NavigationViewTrackingStrategy import com.datadog.android.sample.data.db.LocalDataSource import com.datadog.android.sample.data.remote.RemoteDataSource @@ -44,9 +37,12 @@ import com.datadog.android.sample.picture.CoilImageLoader import com.datadog.android.sample.picture.FrescoImageLoader import com.datadog.android.sample.picture.PicassoImageLoader import com.datadog.android.sample.user.UserFragment +import com.datadog.android.sessionreplay.ImagePrivacy import com.datadog.android.sessionreplay.SessionReplay import com.datadog.android.sessionreplay.SessionReplayConfiguration import com.datadog.android.sessionreplay.SessionReplayPrivacy +import com.datadog.android.sessionreplay.TextAndInputPrivacy +import com.datadog.android.sessionreplay.TouchPrivacy import com.datadog.android.sessionreplay.material.MaterialExtensionSupport import com.datadog.android.timber.DatadogTree import com.datadog.android.trace.AndroidTracer @@ -69,11 +65,12 @@ import retrofit2.Retrofit import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import timber.log.Timber +import java.security.SecureRandom /** * The main [Application] for the sample project. */ -@Suppress("MagicNumber") +@Suppress("MagicNumber", "TooManyFunctions") class SampleApplication : Application() { private val tracedHosts = listOf( @@ -157,43 +154,13 @@ class SampleApplication : Application() { val rumConfig = createRumConfiguration() Rum.enable(rumConfig) - @Suppress("DEPRECATION") - val sessionReplayConfig = SessionReplayConfiguration.Builder(SAMPLE_IN_ALL_SESSIONS) - .apply { - if (BuildConfig.DD_OVERRIDE_SESSION_REPLAY_URL.isNotBlank()) { - useCustomEndpoint(BuildConfig.DD_OVERRIDE_SESSION_REPLAY_URL) - } - } - .setPrivacy(SessionReplayPrivacy.MASK_USER_INPUT) - .addExtensionSupport(MaterialExtensionSupport()) - .build() - SessionReplay.enable(sessionReplayConfig) - - val logsConfig = LogsConfiguration.Builder().apply { - if (BuildConfig.DD_OVERRIDE_LOGS_URL.isNotBlank()) { - useCustomEndpoint(BuildConfig.DD_OVERRIDE_LOGS_URL) - } - }.build() - Logs.enable(logsConfig) - - val tracesConfig = TraceConfiguration.Builder().apply { - if (BuildConfig.DD_OVERRIDE_TRACES_URL.isNotBlank()) { - useCustomEndpoint(BuildConfig.DD_OVERRIDE_TRACES_URL) - } - }.build() - Trace.enable(tracesConfig) + initializeSessionReplay() + initializeLogs() + initializeTraces() NdkCrashReports.enable() - Datadog.setUserInfo( - id = preferences.getUserId(), - name = preferences.getUserName(), - email = preferences.getUserEmail(), - extraInfo = mapOf( - UserFragment.GENDER_KEY to preferences.getUserGender(), - UserFragment.AGE_KEY to preferences.getUserAge() - ) - ) + initializeUserInfo(preferences) GlobalTracer.registerIfAbsent( AndroidTracer.Builder() @@ -217,6 +184,97 @@ class SampleApplication : Application() { TracingRxJava3Utils.enableTracing(GlobalTracer.get()) } + private fun initializeUserInfo(preferences: Preferences.DefaultPreferences) { + Datadog.setUserInfo( + id = preferences.getUserId(), + name = preferences.getUserName(), + email = preferences.getUserEmail(), + extraInfo = mapOf( + UserFragment.GENDER_KEY to preferences.getUserGender(), + UserFragment.AGE_KEY to preferences.getUserAge() + ) + ) + } + + private fun initializeTraces() { + val tracesConfig = TraceConfiguration.Builder().apply { + if (BuildConfig.DD_OVERRIDE_TRACES_URL.isNotBlank()) { + useCustomEndpoint(BuildConfig.DD_OVERRIDE_TRACES_URL) + } + }.build() + Trace.enable(tracesConfig) + } + + private fun initializeLogs() { + val logsConfig = LogsConfiguration.Builder().apply { + if (BuildConfig.DD_OVERRIDE_LOGS_URL.isNotBlank()) { + useCustomEndpoint(BuildConfig.DD_OVERRIDE_LOGS_URL) + } + }.build() + Logs.enable(logsConfig) + } + + private fun initializeSessionReplay() { + val shouldUseFgm = SecureRandom().nextInt(100) < USE_FGM_PCT + + @Suppress("DEPRECATION") + val sessionReplayConfig = SessionReplayConfiguration.Builder(SAMPLE_IN_ALL_SESSIONS) + .apply { + if (BuildConfig.DD_OVERRIDE_SESSION_REPLAY_URL.isNotBlank()) { + useCustomEndpoint(BuildConfig.DD_OVERRIDE_SESSION_REPLAY_URL) + } + + if (shouldUseFgm) { + useFgmConfiguration(this) + } else { + useLegacyConfiguration(this) + } + } + .setPrivacy(SessionReplayPrivacy.MASK_USER_INPUT) + .addExtensionSupport(MaterialExtensionSupport()) + .build() + SessionReplay.enable(sessionReplayConfig) + } + + private fun useFgmConfiguration(builder: SessionReplayConfiguration.Builder) { + val shouldMaskAll = SecureRandom().nextInt(100) < MASK_SESSION_PCT // 25% + + val imagePrivacy = if (shouldMaskAll) { + ImagePrivacy.MASK_ALL + } else { + ImagePrivacy.MASK_NONE + } + + val textAndInputPrivacy = if (shouldMaskAll) { + TextAndInputPrivacy.MASK_ALL + } else { + TextAndInputPrivacy.MASK_SENSITIVE_INPUTS + } + + val touchPrivacy = if (shouldMaskAll) { + TouchPrivacy.HIDE + } else { + TouchPrivacy.SHOW + } + + GlobalRumMonitor.get().addAttribute("imagePrivacy", imagePrivacy) + GlobalRumMonitor.get().addAttribute("textAndInputPrivacy", textAndInputPrivacy) + GlobalRumMonitor.get().addAttribute("touchPrivacy", touchPrivacy) + + builder.setImagePrivacy(imagePrivacy) + builder.setTouchPrivacy(touchPrivacy) + builder.setTextAndInputPrivacy(textAndInputPrivacy) + } + + @Suppress("Deprecation") + private fun useLegacyConfiguration(builder: SessionReplayConfiguration.Builder) { + if (SecureRandom().nextInt(100) <= SESSION_REPLAY_PRIVACY_SAMPLING) { + builder.setPrivacy(SessionReplayPrivacy.ALLOW) + } else { + builder.setPrivacy(SessionReplayPrivacy.MASK_USER_INPUT) + } + } + private fun createRumConfiguration(): RumConfiguration { return RumConfiguration.Builder(BuildConfig.DD_RUM_APPLICATION_ID) .apply { @@ -235,36 +293,26 @@ class SampleApplication : Application() { .trackUserInteractions() .trackLongTasks(250L) .trackNonFatalAnrs(true) - .setViewEventMapper(object : ViewEventMapper { - override fun map(event: ViewEvent): ViewEvent { - event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) - return event - } - }) - .setActionEventMapper(object : EventMapper { - override fun map(event: ActionEvent): ActionEvent { - event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) - return event - } - }) - .setResourceEventMapper(object : EventMapper { - override fun map(event: ResourceEvent): ResourceEvent { - event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) - return event - } - }) - .setErrorEventMapper(object : EventMapper { - override fun map(event: ErrorEvent): ErrorEvent { - event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) - return event - } - }) - .setLongTaskEventMapper(object : EventMapper { - override fun map(event: LongTaskEvent): LongTaskEvent { - event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) - return event - } - }) + .setViewEventMapper { event -> + event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) + event + } + .setActionEventMapper { event -> + event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) + event + } + .setResourceEventMapper { event -> + event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) + event + } + .setErrorEventMapper { event -> + event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) + event + } + .setLongTaskEventMapper { event -> + event.context?.additionalProperties?.put(ATTR_IS_MAPPED, true) + event + } .build() } @@ -327,7 +375,10 @@ class SampleApplication : Application() { } companion object { + private const val USE_FGM_PCT = 10 private const val SAMPLE_IN_ALL_SESSIONS = 100f + private const val MASK_SESSION_PCT = 25 + private const val SESSION_REPLAY_PRIVACY_SAMPLING = 75 init { System.loadLibrary("datadog-native-sample-lib") @@ -354,7 +405,7 @@ class SampleApplication : Application() { return application.retrofitBaseDataSource } - internal fun getLocalServer(context: Context): LocalServer { + private fun getLocalServer(context: Context): LocalServer { val application = context.applicationContext as SampleApplication return application.localServer } From 00798551eef4b0780c9c91dad9addc2b76c411f2 Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Wed, 3 Jul 2024 09:53:19 +0200 Subject: [PATCH 032/111] Update custom detekt CI Job --- .gitignore | 1 + .gitlab-ci.yml | 49 ++++++-- build.gradle.kts | 4 + .../gradle/config/DetektCustomConfig.kt | 115 ++++++++++++++++++ dd-sdk-android-core/build.gradle.kts | 2 + dd-sdk-android-internal/build.gradle.kts | 2 + features/dd-sdk-android-logs/build.gradle.kts | 3 + features/dd-sdk-android-ndk/build.gradle.kts | 2 + features/dd-sdk-android-rum/build.gradle.kts | 3 + .../build.gradle.kts | 2 + .../build.gradle.kts | 3 + .../build.gradle.kts | 3 + .../dd-sdk-android-trace/build.gradle.kts | 3 + .../dd-sdk-android-webview/build.gradle.kts | 2 + .../dd-sdk-android-coil/build.gradle.kts | 2 + .../dd-sdk-android-compose/build.gradle.kts | 2 + .../dd-sdk-android-fresco/build.gradle.kts | 2 + .../dd-sdk-android-glide/build.gradle.kts | 7 ++ .../build.gradle.kts | 7 ++ .../dd-sdk-android-okhttp/build.gradle.kts | 7 ++ .../build.gradle.kts | 2 + .../dd-sdk-android-rx/build.gradle.kts | 2 + .../build.gradle.kts | 7 ++ .../dd-sdk-android-timber/build.gradle.kts | 2 + .../build.gradle.kts | 2 + .../dd-sdk-android-tv/build.gradle.kts | 2 + 26 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 buildSrc/src/main/kotlin/com/datadog/gradle/config/DetektCustomConfig.kt diff --git a/.gitignore b/.gitignore index 96021db4d2..bb3700d431 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ config/* gh_token sdk_classpath +detekt_classpath diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aca970c394..5ce3f1b459 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,7 +15,7 @@ variables: DD_COMMON_AGENT_CONFIG: "dd.env=ci,dd.trace.enabled=false,dd.jmx.fetch.enabled=false" KUBERNETES_MEMORY_REQUEST: "8Gi" - KUBERNETES_MEMORY_LIMIT: "16Gi" + KUBERNETES_MEMORY_LIMIT: "13Gi" EMULATOR_NAME: "android_emulator" ANDROID_ARCH: "arm64-v8a" @@ -110,9 +110,6 @@ static-analysis: stage: analysis variables: DETEKT_PUBLIC_API: "true" - DETEKT_CUSTOM_RULES_BUILD_TASK: "assembleLibrariesRelease :tools:detekt:jar" - DETEKT_CUSTOM_RULES_JAR_PATH: "tools/detekt/build/libs/detekt.jar" - DETEKT_CUSTOM_RULES_YML_PATH: "detekt_custom.yml" DETEKT_GENERATE_CLASSPATH_BUILD_TASK: "printSdkDebugRuntimeClasspath" DETEKT_CLASSPATH_FILE_PATH: "sdk_classpath" FLAVORED_ANDROID_LINT: ":tools:lint:lint" @@ -120,6 +117,42 @@ static-analysis: include: "https://gitlab-templates.ddbuild.io/mobile/v34714656-060be019/static-analysis.yml" strategy: depend +analysis:detekt-custom: + tags: + - "arch:amd64" + image: $CI_IMAGE_DOCKER + stage: analysis + timeout: 1h + script: + - ./gradlew assembleLibrariesRelease --stacktrace + - ./gradlew unzipAarForDetekt --stacktrace + - ./gradlew :tools:detekt:jar --stacktrace + - ./gradlew printDetektClasspath --stacktrace + - curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.4/detekt-cli-1.23.4-all.jar + - ./gradlew :dd-sdk-android-core:customDetektRules + - ./gradlew :dd-sdk-android-internal:customDetektRules + - ./gradlew :features:dd-sdk-android-logs:customDetektRules + - ./gradlew :features:dd-sdk-android-ndk:customDetektRules + - ./gradlew :features:dd-sdk-android-rum:customDetektRules + - ./gradlew :features:dd-sdk-android-session-replay:customDetektRules + - ./gradlew :features:dd-sdk-android-session-replay-material:customDetektRules + - ./gradlew :features:dd-sdk-android-trace:customDetektRules + - ./gradlew :features:dd-sdk-android-trace-otel:customDetektRules + - ./gradlew :features:dd-sdk-android-webview:customDetektRules + - ./gradlew :integrations:dd-sdk-android-coil:customDetektRules + - ./gradlew :integrations:dd-sdk-android-compose:customDetektRules + - ./gradlew :integrations:dd-sdk-android-fresco:customDetektRules + - ./gradlew :integrations:dd-sdk-android-glide:customDetektRules + - ./gradlew :integrations:dd-sdk-android-okhttp:customDetektRules + - ./gradlew :integrations:dd-sdk-android-okhttp-otel:customDetektRules + - ./gradlew :integrations:dd-sdk-android-rum-coroutines:customDetektRules + - ./gradlew :integrations:dd-sdk-android-rx:customDetektRules + - ./gradlew :integrations:dd-sdk-android-sqldelight:customDetektRules + - ./gradlew :integrations:dd-sdk-android-timber:customDetektRules + - ./gradlew :integrations:dd-sdk-android-trace-coroutines:customDetektRules + - ./gradlew :integrations:dd-sdk-android-tv:customDetektRules + + # TODO RUM-1622 cleanup this section # TESTS @@ -138,6 +171,7 @@ test:debug: - rm -rf ~/.gradle/daemon/ - export DD_AGENT_HOST="$BUILDENV_HOST_IP" - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:debug" ./gradlew :dd-sdk-android-core:testDebugUnitTest --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError + - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:debug" ./gradlew :dd-sdk-android-internal:testDebugUnitTest --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:debug" ./gradlew :unitTestDebugFeatures --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:debug" ./gradlew :unitTestDebugIntegrations --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError artifacts: @@ -182,6 +216,7 @@ test:kover: - export DD_APP_KEY=$(aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android.app_key --with-decryption --query "Parameter.Value" --out text) - CODECOV_TOKEN=$(aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android.codecov-token --with-decryption --query "Parameter.Value" --out text) - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:release" ./gradlew :dd-sdk-android-core:koverXmlReportRelease --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError + - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:release" ./gradlew :dd-sdk-android-internal:koverXmlReportRelease --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:release" ./gradlew :koverReportFeatures --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError - GRADLE_OPTS="-Xmx3072m" DD_TAGS="test.configuration.variant:release" ./gradlew :koverReportIntegrations --no-daemon --build-cache --gradle-user-home cache/ -Dorg.gradle.jvmargs=-javaagent:$DD_TRACER_FOLDER/dd-java-agent.jar=$DD_COMMON_AGENT_CONFIG -Dorg.gradle.jvmargs=-XX:+HeapDumpOnOutOfMemoryError - bash <(cat ./codecov.sh) -t $CODECOV_TOKEN @@ -779,9 +814,9 @@ notify:dogfood-app: stage: notify when: on_success script: - - pip3 install GitPython requests - - aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android.gh_token --with-decryption --query "Parameter.Value" --out text >> ./gh_token - - python3 dogfood.py -v $CI_COMMIT_TAG -t app + - pip3 install GitPython requests + - aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android.gh_token --with-decryption --query "Parameter.Value" --out text >> ./gh_token + - python3 dogfood.py -v $CI_COMMIT_TAG -t app notify:dogfood-demo: tags: [ "arch:amd64" ] diff --git a/build.gradle.kts b/build.gradle.kts index 3bbe1a136b..9987551f03 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -137,6 +137,10 @@ registerSubModuleAggregationTask("koverReportAll", "koverXmlReportRelease") registerSubModuleAggregationTask("koverReportFeatures", "koverXmlReportRelease", ":features:") registerSubModuleAggregationTask("koverReportIntegrations", "koverXmlReportRelease", ":integrations:") +registerSubModuleAggregationTask("printDetektClasspathAll", "printDetektClasspath") +registerSubModuleAggregationTask("printDetektClasspathFeatures", "printDetektClasspath", ":features:") +registerSubModuleAggregationTask("printDetektClasspathIntegrations", "printDetektClasspath", ":integrations:") + tasks.register("instrumentTestAll") { dependsOn(":instrumented:integration:connectedCheck") } diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/config/DetektCustomConfig.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/config/DetektCustomConfig.kt new file mode 100644 index 0000000000..e7d525be96 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/config/DetektCustomConfig.kt @@ -0,0 +1,115 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.gradle.config + +import com.android.build.gradle.LibraryExtension +import org.gradle.api.Project +import org.gradle.api.file.FileCollection +import org.gradle.api.file.FileTree +import org.gradle.api.internal.file.UnionFileTree +import org.gradle.api.internal.tasks.DefaultTaskDependencyFactory +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.JavaExec +import java.io.File +import java.util.Properties + +fun Project.detektCustomConfig( + vararg moduleDependencies: String +) { + val ext = extensions.findByType(LibraryExtension::class.java) + + tasks.register("printDetektClasspath") { + group = "datadog" + + doLast { + val fileTreeClassPathCollector = UnionFileTree( + DefaultTaskDependencyFactory.withNoAssociatedProject() + ) + val nonFileTreeClassPathCollector = mutableListOf() + + val classpath = ext?.libraryVariants.orEmpty() + .filter { it.name == "jvmDebug" || it.name == "debug" } + .map { libVariant -> + // returns also test part of classpath for now, no idea how to filter it out + libVariant.getCompileClasspath(null).filter { it.exists() } + } + .firstOrNull() + + if (classpath is FileTree) { + fileTreeClassPathCollector.addToUnion(classpath) + } else if (classpath != null) { + nonFileTreeClassPathCollector += classpath + } + + val fileCollections = mutableListOf() + fileCollections.addAll(nonFileTreeClassPathCollector) + if (!fileTreeClassPathCollector.isEmpty) { + fileCollections.add(fileTreeClassPathCollector) + } + val result = fileCollections.flatMap { + it.files + }.toMutableSet() + val localPropertiesFile = File(project.rootDir, "local.properties") + if (localPropertiesFile.exists()) { + val localProperties = Properties().apply { + localPropertiesFile.inputStream().use { load(it) } + } + val sdkDirPath = localProperties["sdk.dir"] + val androidJarFilePath = listOf( + sdkDirPath, + "platforms", + "android-${AndroidConfig.TARGET_SDK}", + "android.jar" + ) + result += File(androidJarFilePath.joinToString(File.separator)) + } + val envSdkHome = System.getenv("ANDROID_SDK_ROOT") + if (!envSdkHome.isNullOrBlank()) { + val androidJarFilePath = listOf( + envSdkHome, + "platforms", + "android-${AndroidConfig.TARGET_SDK}", + "android.jar" + ) + result += File(androidJarFilePath.joinToString(File.separator)) + } + + val output = result.joinToString(File.pathSeparator) { it.absolutePath } + File(projectDir, "detekt_classpath").writeText(output) + } + } + + tasks.register("unzipAarForDetekt", Copy::class.java) { + from(zipTree(layout.buildDirectory.file("outputs/aar/${project.name}-release.aar"))) + into(layout.buildDirectory.dir("extracted")) + } + + tasks.register("customDetektRules", JavaExec::class.java) { + group = "datadog" + + classpath = files("${rootDir.absolutePath}/detekt-cli-1.23.4-all.jar") + + args("--config", "${rootDir.absolutePath}/detekt_custom.yml") + args("--plugins", "${rootDir.absolutePath}/tools/detekt/build/libs/detekt.jar") + args("-i", projectDir.absolutePath) + args("-ex", "**/*.kts") + args("--jvm-target", "11") + + val externalDependencies = File("${projectDir.absolutePath}/detekt_classpath").readText() + val moduleDependenciesClasses = moduleDependencies.map { + "${rootDir.absolutePath}${it.replace(':', '/')}/build/extracted/classes.jar" + }.joinToString(":") + + val dependencies = if (moduleDependenciesClasses.isBlank()) { + externalDependencies + } else { + "$externalDependencies:$moduleDependenciesClasses" + } + + args("-cp", dependencies) + } +} diff --git a/dd-sdk-android-core/build.gradle.kts b/dd-sdk-android-core/build.gradle.kts index 5fb7840a38..d1f55d12bb 100644 --- a/dd-sdk-android-core/build.gradle.kts +++ b/dd-sdk-android-core/build.gradle.kts @@ -9,6 +9,7 @@ import com.datadog.gradle.config.BuildConfigPropertiesKeys import com.datadog.gradle.config.GradlePropertiesKeys import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -151,3 +152,4 @@ junitConfig() javadocConfig() dependencyUpdateConfig() publishingConfig("Datadog monitoring library for Android applications.") +detektCustomConfig() diff --git a/dd-sdk-android-internal/build.gradle.kts b/dd-sdk-android-internal/build.gradle.kts index ff3ea51422..a9ff7bd718 100644 --- a/dd-sdk-android-internal/build.gradle.kts +++ b/dd-sdk-android-internal/build.gradle.kts @@ -1,5 +1,6 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.java17 import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig @@ -68,3 +69,4 @@ dependencyUpdateConfig() publishingConfig( "Internal library to be used by the Datadog SDK modules." ) +detektCustomConfig() diff --git a/features/dd-sdk-android-logs/build.gradle.kts b/features/dd-sdk-android-logs/build.gradle.kts index ecb8e5caca..95312d0d72 100644 --- a/features/dd-sdk-android-logs/build.gradle.kts +++ b/features/dd-sdk-android-logs/build.gradle.kts @@ -3,9 +3,11 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ +@file:Suppress("StringLiteralDuplication") import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -86,3 +88,4 @@ publishingConfig( "The Logs feature to use with the Datadog monitoring " + "library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal") diff --git a/features/dd-sdk-android-ndk/build.gradle.kts b/features/dd-sdk-android-ndk/build.gradle.kts index 45358199f8..07721a769c 100644 --- a/features/dd-sdk-android-ndk/build.gradle.kts +++ b/features/dd-sdk-android-ndk/build.gradle.kts @@ -8,6 +8,7 @@ import com.datadog.gradle.Dependencies import com.datadog.gradle.config.AndroidConfig import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -108,3 +109,4 @@ dependencyUpdateConfig() publishingConfig( "An NDK integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core") diff --git a/features/dd-sdk-android-rum/build.gradle.kts b/features/dd-sdk-android-rum/build.gradle.kts index a67682c4bc..4d8722bb93 100644 --- a/features/dd-sdk-android-rum/build.gradle.kts +++ b/features/dd-sdk-android-rum/build.gradle.kts @@ -3,9 +3,11 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ +@file:Suppress("StringLiteralDuplication") import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -115,3 +117,4 @@ publishingConfig( "The RUM feature to use with the Datadog monitoring " + "library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal") diff --git a/features/dd-sdk-android-session-replay-material/build.gradle.kts b/features/dd-sdk-android-session-replay-material/build.gradle.kts index cbafe97b61..48fae71501 100644 --- a/features/dd-sdk-android-session-replay-material/build.gradle.kts +++ b/features/dd-sdk-android-session-replay-material/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -69,3 +70,4 @@ dependencyUpdateConfig() publishingConfig( "Session Replay Extension Support for Material UI components." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-session-replay") diff --git a/features/dd-sdk-android-session-replay/build.gradle.kts b/features/dd-sdk-android-session-replay/build.gradle.kts index 25ed55372e..37b4ae17ff 100644 --- a/features/dd-sdk-android-session-replay/build.gradle.kts +++ b/features/dd-sdk-android-session-replay/build.gradle.kts @@ -3,9 +3,11 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ +@file:Suppress("StringLiteralDuplication") import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -88,3 +90,4 @@ publishingConfig( "The Session Replay feature to use with the Datadog monitoring " + "library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal") diff --git a/features/dd-sdk-android-trace-otel/build.gradle.kts b/features/dd-sdk-android-trace-otel/build.gradle.kts index ef6f5d91e7..848a3d714f 100644 --- a/features/dd-sdk-android-trace-otel/build.gradle.kts +++ b/features/dd-sdk-android-trace-otel/build.gradle.kts @@ -3,9 +3,11 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ +@file:Suppress("StringLiteralDuplication") import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -86,3 +88,4 @@ dependencyUpdateConfig() publishingConfig( "The tracing library for Android, providing OpenTelemetry compatibility." ) +detektCustomConfig(":dd-sdk-android-core", ":features:dd-sdk-android-trace") diff --git a/features/dd-sdk-android-trace/build.gradle.kts b/features/dd-sdk-android-trace/build.gradle.kts index 519af86c8a..93e879fd1f 100644 --- a/features/dd-sdk-android-trace/build.gradle.kts +++ b/features/dd-sdk-android-trace/build.gradle.kts @@ -3,9 +3,11 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). * Copyright 2016-Present Datadog, Inc. */ +@file:Suppress("StringLiteralDuplication") import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -91,3 +93,4 @@ publishingConfig( "The Tracing feature to use with the Datadog monitoring " + "library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal") diff --git a/features/dd-sdk-android-webview/build.gradle.kts b/features/dd-sdk-android-webview/build.gradle.kts index 0c295c290d..380b4a4b65 100644 --- a/features/dd-sdk-android-webview/build.gradle.kts +++ b/features/dd-sdk-android-webview/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -80,3 +81,4 @@ publishingConfig( "The WebView integration feature to use with the Datadog monitoring " + "library for Android applications." ) +detektCustomConfig(":dd-sdk-android-internal") diff --git a/integrations/dd-sdk-android-coil/build.gradle.kts b/integrations/dd-sdk-android-coil/build.gradle.kts index 423faba3da..c6884498f3 100644 --- a/integrations/dd-sdk-android-coil/build.gradle.kts +++ b/integrations/dd-sdk-android-coil/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -66,3 +67,4 @@ dependencyUpdateConfig() publishingConfig( "A Coil integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-rum") diff --git a/integrations/dd-sdk-android-compose/build.gradle.kts b/integrations/dd-sdk-android-compose/build.gradle.kts index 24c863c016..37703374c0 100644 --- a/integrations/dd-sdk-android-compose/build.gradle.kts +++ b/integrations/dd-sdk-android-compose/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -87,3 +88,4 @@ publishingConfig( "A Jetpack Compose integration to use with the Datadog monitoring library" + " for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-rum") diff --git a/integrations/dd-sdk-android-fresco/build.gradle.kts b/integrations/dd-sdk-android-fresco/build.gradle.kts index f36fba4520..7986036a1e 100644 --- a/integrations/dd-sdk-android-fresco/build.gradle.kts +++ b/integrations/dd-sdk-android-fresco/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -66,3 +67,4 @@ dependencyUpdateConfig() publishingConfig( "A Fresco integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-rum") diff --git a/integrations/dd-sdk-android-glide/build.gradle.kts b/integrations/dd-sdk-android-glide/build.gradle.kts index 17fa20e802..786f6db6c1 100644 --- a/integrations/dd-sdk-android-glide/build.gradle.kts +++ b/integrations/dd-sdk-android-glide/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -67,3 +68,9 @@ dependencyUpdateConfig() publishingConfig( "A Glide integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig( + ":dd-sdk-android-core", + ":dd-sdk-android-internal", + ":features:dd-sdk-android-rum", + ":integrations:dd-sdk-android-okhttp" +) diff --git a/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts b/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts index cf119391c4..adbf9615c1 100644 --- a/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts +++ b/integrations/dd-sdk-android-okhttp-otel/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -66,3 +67,9 @@ dependencyUpdateConfig() publishingConfig( "An OkHttp collection of extensions to be used in conjunction with OpenTelemetry Datadog SDK." ) +detektCustomConfig( + ":dd-sdk-android-core", + ":dd-sdk-android-internal", + ":features:dd-sdk-android-trace-otel", + ":integrations:dd-sdk-android-okhttp" +) diff --git a/integrations/dd-sdk-android-okhttp/build.gradle.kts b/integrations/dd-sdk-android-okhttp/build.gradle.kts index 02931e1c04..8c098eaa5b 100644 --- a/integrations/dd-sdk-android-okhttp/build.gradle.kts +++ b/integrations/dd-sdk-android-okhttp/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -79,3 +80,9 @@ dependencyUpdateConfig() publishingConfig( "An OkHttp monitoring integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig( + ":dd-sdk-android-core", + ":dd-sdk-android-internal", + ":features:dd-sdk-android-rum", + ":features:dd-sdk-android-trace" +) diff --git a/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts b/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts index 7f87bfdf5f..dfdac574df 100644 --- a/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts +++ b/integrations/dd-sdk-android-rum-coroutines/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -64,3 +65,4 @@ dependencyUpdateConfig() publishingConfig( "A RUM Coroutines extension library to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-rum") diff --git a/integrations/dd-sdk-android-rx/build.gradle.kts b/integrations/dd-sdk-android-rx/build.gradle.kts index c8ef838c53..742f403ee9 100644 --- a/integrations/dd-sdk-android-rx/build.gradle.kts +++ b/integrations/dd-sdk-android-rx/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -66,3 +67,4 @@ dependencyUpdateConfig() publishingConfig( "A RxJava integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-rum") diff --git a/integrations/dd-sdk-android-sqldelight/build.gradle.kts b/integrations/dd-sdk-android-sqldelight/build.gradle.kts index a3e41b37ac..a0584cef3d 100644 --- a/integrations/dd-sdk-android-sqldelight/build.gradle.kts +++ b/integrations/dd-sdk-android-sqldelight/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -67,3 +68,9 @@ dependencyUpdateConfig() publishingConfig( "A SQLDelight integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig( + ":dd-sdk-android-core", + ":dd-sdk-android-internal", + ":features:dd-sdk-android-rum", + ":features:dd-sdk-android-trace" +) diff --git a/integrations/dd-sdk-android-timber/build.gradle.kts b/integrations/dd-sdk-android-timber/build.gradle.kts index ca7f778893..850e79329a 100644 --- a/integrations/dd-sdk-android-timber/build.gradle.kts +++ b/integrations/dd-sdk-android-timber/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -65,3 +66,4 @@ dependencyUpdateConfig() publishingConfig( "A Timber integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-logs") diff --git a/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts b/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts index e77299e7f6..fda6dcbab6 100644 --- a/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts +++ b/integrations/dd-sdk-android-trace-coroutines/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -64,3 +65,4 @@ dependencyUpdateConfig() publishingConfig( "A Trace Coroutines extension library to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-trace") diff --git a/integrations/dd-sdk-android-tv/build.gradle.kts b/integrations/dd-sdk-android-tv/build.gradle.kts index e7a5fd88d9..f7b459b1b3 100644 --- a/integrations/dd-sdk-android-tv/build.gradle.kts +++ b/integrations/dd-sdk-android-tv/build.gradle.kts @@ -6,6 +6,7 @@ import com.datadog.gradle.config.androidLibraryConfig import com.datadog.gradle.config.dependencyUpdateConfig +import com.datadog.gradle.config.detektCustomConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig import com.datadog.gradle.config.kotlinConfig @@ -64,3 +65,4 @@ dependencyUpdateConfig() publishingConfig( "An Android TV integration to use with the Datadog monitoring library for Android applications." ) +detektCustomConfig(":dd-sdk-android-core", ":dd-sdk-android-internal", ":features:dd-sdk-android-rum") From fb3631447bcfc704dc99850b0f28c556ed96e3df Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Fri, 13 Sep 2024 15:22:17 +0200 Subject: [PATCH 033/111] Remove verbose println in custom Detekt rules --- .../datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt | 6 ------ .../com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt | 3 --- 2 files changed, 9 deletions(-) diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt index 3f90d31381..f831028190 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/PackageNameVisibility.kt @@ -20,7 +20,6 @@ import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtPackageDirective import org.jetbrains.kotlin.psi.KtProperty -import org.jetbrains.kotlin.util.isAnnotated /** * A rule to detekt classes in the wrong package name. @@ -57,11 +56,6 @@ class PackageNameVisibility( override fun visitNamedDeclaration(decl: KtNamedDeclaration) { val isDeclarationInternal = decl.isPrivate() || decl.isInternal() - println("Annotations?: ${decl.isAnnotated}") - decl.annotationEntries.forEach { - val annotationName = it.shortName?.asString()?.resolveFullType() - println("Annotation: $annotationName / ${it.shortName}") - } val isIgnoredAnnotation = decl.annotationEntries.any { it.shortName?.asString()?.resolveFullType() in ignoredAnnotations } diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt index b3593e8575..1455dbaaac 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/ThreadSafety.kt @@ -116,9 +116,6 @@ class ThreadSafety( } else { null } - if (wrapCallWith == null) { - println("Non thread switching call:$call") - } } else { val callee = expression.calleeExpression if (callee is KtNameReferenceExpression) { From e70c3eaf4ef4a3d120c89dc28a7662f2e794bb3c Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Tue, 17 Sep 2024 15:58:06 +0200 Subject: [PATCH 034/111] RUM-6033 Add telemetry and logs related with RumMonitor#addViewLoadingTime API --- dd-sdk-android-core/api/apiSurface | 2 +- .../api/dd-sdk-android-core.api | 1 + .../com/datadog/android/api/InternalLogger.kt | 4 +- .../android/rum/internal/RumFeature.kt | 2 +- .../domain/event/RumEventSerializer.kt | 4 + .../domain/scope/RumViewManagerScope.kt | 20 ++- .../rum/internal/domain/scope/RumViewScope.kt | 87 ++++++++++--- .../internal/TelemetryEventHandler.kt | 2 +- .../domain/event/RumEventSerializerTest.kt | 110 ++++++++++++++++ .../internal/domain/scope/RumRawEventExt.kt | 10 +- .../domain/scope/RumViewManagerScopeTest.kt | 36 ++++++ .../internal/domain/scope/RumViewScopeTest.kt | 121 ++++++++++++++++++ .../android/rum/utils/InternalLoggerUtils.kt | 13 ++ .../InternalAddViewLoadingTimeEventAssert.kt | 73 +++++++++++ .../assertj/InternalApiUsageEventAssert.kt | 38 ++++++ .../internal/TelemetryEventHandlerTest.kt | 16 +-- 16 files changed, 501 insertions(+), 38 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt diff --git a/dd-sdk-android-core/api/apiSurface b/dd-sdk-android-core/api/apiSurface index 669c3cd39a..a154fffe35 100644 --- a/dd-sdk-android-core/api/apiSurface +++ b/dd-sdk-android-core/api/apiSurface @@ -44,7 +44,7 @@ interface com.datadog.android.api.InternalLogger fun log(Level, List, () -> String, Throwable? = null, Boolean = false, Map? = null) fun logMetric(() -> String, Map, Float) fun startPerformanceMeasure(String, com.datadog.android.core.metrics.TelemetryMetricType, Float, String): com.datadog.android.core.metrics.PerformanceMetric? - fun logApiUsage(com.datadog.android.internal.telemetry.InternalTelemetryEvent.ApiUsage, Float) + fun logApiUsage(com.datadog.android.internal.telemetry.InternalTelemetryEvent.ApiUsage, Float = 15f) companion object val UNBOUND: InternalLogger interface com.datadog.android.api.SdkCore diff --git a/dd-sdk-android-core/api/dd-sdk-android-core.api b/dd-sdk-android-core/api/dd-sdk-android-core.api index 1795e51ba9..b0ee781a8e 100644 --- a/dd-sdk-android-core/api/dd-sdk-android-core.api +++ b/dd-sdk-android-core/api/dd-sdk-android-core.api @@ -89,6 +89,7 @@ public final class com/datadog/android/api/InternalLogger$Companion { public final class com/datadog/android/api/InternalLogger$DefaultImpls { public static synthetic fun log$default (Lcom/datadog/android/api/InternalLogger;Lcom/datadog/android/api/InternalLogger$Level;Lcom/datadog/android/api/InternalLogger$Target;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;ILjava/lang/Object;)V public static synthetic fun log$default (Lcom/datadog/android/api/InternalLogger;Lcom/datadog/android/api/InternalLogger$Level;Ljava/util/List;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;ILjava/lang/Object;)V + public static synthetic fun logApiUsage$default (Lcom/datadog/android/api/InternalLogger;Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage;FILjava/lang/Object;)V } public final class com/datadog/android/api/InternalLogger$Level : java/lang/Enum { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt index f49d4c8229..aa848f1a72 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt @@ -137,12 +137,12 @@ interface InternalLogger { * Logs an API usage from the internal implementation. * @param apiUsageEvent the API event being tracked * @param samplingRate value between 0-100 for sampling the event. Note that the sampling rate applied to this - * event will be applied in addition to the global telemetry sampling rate. + * event will be applied in addition to the global telemetry sampling rate. By default, the sampling rate is 15%. */ @InternalApi fun logApiUsage( apiUsageEvent: InternalTelemetryEvent.ApiUsage, - samplingRate: Float + samplingRate: Float = 15f ) companion object { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 0e64b9c093..c23b1a0320 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -540,7 +540,7 @@ internal class RumFeature( internal const val ALL_IN_SAMPLE_RATE: Float = 100f internal const val DEFAULT_SAMPLE_RATE: Float = 100f internal const val DEFAULT_TELEMETRY_SAMPLE_RATE: Float = 20f - internal const val DEFAULT_TELEMETRY_CONFIGURATION_SAMPLE_RATE: Float = 100f + internal const val DEFAULT_TELEMETRY_CONFIGURATION_SAMPLE_RATE: Float = 20f internal const val DEFAULT_LONG_TASK_THRESHOLD_MS = 100L internal const val DD_TELEMETRY_CONFIG_SAMPLE_RATE_TAG = "_dd.telemetry.configuration_sample_rate" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt index 1b63efac8c..f93fa94670 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt @@ -20,6 +20,7 @@ import com.datadog.android.rum.model.ViewEvent import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import com.google.gson.JsonObject internal class RumEventSerializer( @@ -55,6 +56,9 @@ internal class RumEventSerializer( is TelemetryConfigurationEvent -> { model.toJson().toString() } + is TelemetryUsageEvent -> { + model.toJson().toString() + } is JsonObject -> { model.toString() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt index 881682b67c..ace9fbea48 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt @@ -13,6 +13,7 @@ import com.datadog.android.api.storage.DataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.metrics.MethodCallSamplingRate +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.internal.anr.ANRException import com.datadog.android.rum.internal.domain.RumContext @@ -144,7 +145,24 @@ internal class RumViewManagerScope( val importanceForeground = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND val isForegroundProcess = processFlag == importanceForeground - if (applicationDisplayed || !isForegroundProcess) { + if (event is RumRawEvent.AddViewLoadingTime) { + val internalLogger = sdkCore.internalLogger + internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { MESSAGE_MISSING_VIEW } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = event.overwrite, + noView = true, + noActiveView = false + ) + ) + // we should return here and not add the event to the session ended metric missed events as we already + // send the API usage telemetry + return + } else if (applicationDisplayed || !isForegroundProcess) { handleBackgroundEvent(event, writer) } else { val isSilentOrphanEvent = event.javaClass in silentOrphanEventTypes diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 88624605d9..eb6669a3db 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -241,24 +241,74 @@ internal open class RumViewScope( @WorkerThread private fun onAddViewLoadingTime(event: RumRawEvent.AddViewLoadingTime, writer: DataWriter) { - if (stopped) return - val canAddViewLoadingTime = event.overwrite || viewLoadingTime == null - sdkCore.internalLogger.logApiUsage( - InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( - overwrite = event.overwrite, - noView = viewId.isEmpty(), - noActiveView = !isActive() - ), - 100.0f - ) - if (canAddViewLoadingTime) { - viewLoadingTime = event.eventTime.nanoTime - startedNanos - sendViewUpdate(event, writer) - } else { - // TODO RUM-6031 Add logs and telemetry here + val internalLogger = sdkCore.internalLogger + val canUpdateViewLoadingTime = !stopped && (viewLoadingTime == null || event.overwrite) + if (stopped) { + internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { NO_ACTIVE_VIEW_FOR_LOADING_TIME_WARNING_MESSAGE } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = event.overwrite, + noView = false, + noActiveView = true + ) + ) + } + + if (canUpdateViewLoadingTime) { + updateViewLoadingTime(event, internalLogger, writer) } } + private fun updateViewLoadingTime( + event: RumRawEvent.AddViewLoadingTime, + internalLogger: InternalLogger, + writer: DataWriter + ) { + val viewName = key.name + val previousViewLoadingTime = viewLoadingTime + val newLoadingTime = event.eventTime.nanoTime - startedNanos + if (previousViewLoadingTime == null) { + internalLogger.log( + InternalLogger.Level.DEBUG, + InternalLogger.Target.USER, + { ADDING_VIEW_LOADING_TIME_DEBUG_MESSAGE_FORMAT.format(Locale.US, viewLoadingTime, viewName) } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = false, + noView = false, + noActiveView = false + ) + ) + } else if (event.overwrite) { + internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { + OVERWRITING_VIEW_LOADING_TIME_WARNING_MESSAGE_FORMAT.format( + Locale.US, + viewName, + previousViewLoadingTime, + newLoadingTime + ) + } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = true, + noView = false, + noActiveView = false + ) + ) + } + viewLoadingTime = newLoadingTime + sendViewUpdate(event, writer) + } + @WorkerThread private fun onStartView( event: RumRawEvent.StartView, @@ -1258,6 +1308,13 @@ internal open class RumViewScope( internal const val SLOW_RENDERED_THRESHOLD_FPS = 55 internal const val NEGATIVE_DURATION_WARNING_MESSAGE = "The computed duration for the " + "view: %s was 0 or negative. In order to keep the view we forced it to 1ns." + internal const val NO_ACTIVE_VIEW_FOR_LOADING_TIME_WARNING_MESSAGE = + "No active view found to add the loading time." + internal const val ADDING_VIEW_LOADING_TIME_DEBUG_MESSAGE_FORMAT = + "View loading time %dns added to the view %s" + internal const val OVERWRITING_VIEW_LOADING_TIME_WARNING_MESSAGE_FORMAT = + "View loading time already exists for the view %s. Replacing the existing %d ns " + + "view loading time with the new %d ns loading time." internal fun fromEvent( parentScope: RumScope, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 6db598a351..007f69ced8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -446,7 +446,7 @@ internal class TelemetryEventHandler( companion object { const val MAX_EVENTS_PER_SESSION = 100 - const val DEFAULT_CONFIGURATION_SAMPLE_RATE = 100f + const val DEFAULT_CONFIGURATION_SAMPLE_RATE = 20f const val ALREADY_SEEN_EVENT_MESSAGE = "Already seen telemetry event with identity=%s, rejecting." const val MAX_EVENT_NUMBER_REACHED_MESSAGE = diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt index dfe4188240..019227bc17 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt @@ -19,6 +19,7 @@ import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import com.datadog.tools.unit.assertj.JsonObjectAssert.Companion.assertThat import com.datadog.tools.unit.forge.anException import com.google.gson.JsonObject @@ -33,6 +34,7 @@ import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.api.fail import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings @@ -748,6 +750,110 @@ internal class RumEventSerializerTest { } } + @RepeatedTest(8) + fun `M serialize RUM event W serialize() with TelemetryUsageEvent`( + @Forgery event: TelemetryUsageEvent + ) { + val serialized = testedSerializer.serialize(event) + val jsonObject = JsonParser.parseString(serialized).asJsonObject + assertThat(jsonObject) + .hasField("type", "telemetry") + .hasField("_dd") { + hasField("format_version", 2L) + } + .hasField("date", event.date) + .hasField("source", event.source.name.lowercase(Locale.US).replace('_', '-')) + .hasField("service", event.service) + .hasField("version", event.version) + .hasField("telemetry") { + hasField("usage") { + when (event.telemetry.usage) { + is TelemetryUsageEvent.Usage.AddViewLoadingTime -> { + val usage = event.telemetry.usage as TelemetryUsageEvent.Usage.AddViewLoadingTime + hasField("no_view", usage.noView) + hasField("no_active_view", usage.noActiveView) + hasField("overwritten", usage.overwritten) + } + + else -> { + fail("Usage type not covered in assertions") + } + } + } + if (event.telemetry.device != null) { + hasField("device") { + val device = event.telemetry.device + checkNotNull(device) + if (device.architecture != null) { + hasField("architecture", device.architecture!!) + } + if (device.brand != null) { + hasField("brand", device.brand!!) + } + if (device.model != null) { + hasField("model", device.model!!) + } + } + } + if (event.telemetry.os != null) { + hasField("os") { + val os = event.telemetry.os + checkNotNull(os) + if (os.build != null) { + hasField("build", os.build!!) + } + if (os.name != null) { + hasField("name", os.name!!) + } + if (os.version != null) { + hasField("version", os.version!!) + } + } + } + containsAttributes(event.telemetry.additionalProperties) + } + + val application = event.application + if (application != null) { + assertThat(jsonObject) + .hasField("application") { + hasField("id", application.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("application") + } + + val session = event.session + if (session != null) { + assertThat(jsonObject) + .hasField("session") { + hasField("id", session.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("session") + } + + val view = event.view + if (view != null) { + assertThat(jsonObject) + .hasField("view") { + hasField("id", view.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("view") + } + + val action = event.action + if (action != null) { + assertThat(jsonObject) + .hasField("action") { + hasField("id", action.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("action") + } + } + @Test fun `M serialize RUM event W serialize() with unknown event`( @Forgery unknownEvent: UserInfo @@ -1451,18 +1557,21 @@ internal class RumEventSerializerTest { usr = (it.usr ?: ViewEvent.Usr()).copy(additionalProperties = userAttributes) ) } + 2 -> this.getForgery(ActionEvent::class.java).let { it.copy( context = ActionEvent.Context(additionalProperties = attributes), usr = (it.usr ?: ActionEvent.Usr()).copy(additionalProperties = userAttributes) ) } + 3 -> this.getForgery(ErrorEvent::class.java).let { it.copy( context = ErrorEvent.Context(additionalProperties = attributes), usr = (it.usr ?: ErrorEvent.Usr()).copy(additionalProperties = userAttributes) ) } + 4 -> this.getForgery(ResourceEvent::class.java).let { it.copy( context = ResourceEvent.Context(additionalProperties = attributes), @@ -1470,6 +1579,7 @@ internal class RumEventSerializerTest { .copy(additionalProperties = userAttributes) ) } + else -> this.getForgery(LongTaskEvent::class.java).let { it.copy( context = LongTaskEvent.Context(additionalProperties = attributes), diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt index 22a669d234..1db369cb73 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt @@ -112,6 +112,10 @@ internal fun Forge.addErrorEvent(): RumRawEvent.AddError { ) } +internal fun Forge.addViewLoadingTimeEvent(): RumRawEvent.AddViewLoadingTime { + return RumRawEvent.AddViewLoadingTime(overwrite = aBool()) +} + internal fun Forge.addLongTaskEvent(): RumRawEvent.AddLongTask { return RumRawEvent.AddLongTask( durationNs = aLong(min = 1), @@ -178,7 +182,8 @@ internal fun Forge.invalidBackgroundEvent(): RumRawEvent { stopActionEvent(), stopResourceEvent(), stopResourceWithErrorEvent(), - stopResourceWithStacktraceEvent() + stopResourceWithStacktraceEvent(), + addViewLoadingTimeEvent() ) ) } @@ -197,7 +202,8 @@ internal fun Forge.anyRumEvent(excluding: List = listOf()): RumRawEvent { addLongTaskEvent(), addFeatureFlagEvaluationEvent(), addCustomTimingEvent(), - updatePerformanceMetricEvent() + updatePerformanceMetricEvent(), + addViewLoadingTimeEvent() ) return this.anElementFrom( allEvents.filter { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt index 3b6a0f7452..1f67cea660 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt @@ -17,6 +17,7 @@ import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.internal.anr.ANRDetectorRunnable @@ -28,6 +29,7 @@ import com.datadog.android.rum.internal.vitals.NoOpVitalMonitor import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.android.rum.utils.verifyApiUsage import com.datadog.android.rum.utils.verifyLog import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery @@ -55,6 +57,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.concurrent.TimeUnit @@ -818,6 +821,39 @@ internal class RumViewManagerScopeTest { // endregion + // region AddViewLoadingTime + + @Test + fun `M send a warning log and api usage telemetry W handleEvent { AddViewLoadingTime, no active view }`( + forge: Forge + ) { + // Given + val fakeEvent = forge.addViewLoadingTimeEvent() + testedScope.applicationDisplayed = true + + // When + testedScope.handleEvent(fakeEvent, mockWriter) + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + RumViewManagerScope.MESSAGE_MISSING_VIEW + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = fakeEvent.overwrite, + noView = true, + noActiveView = false + ), + 15f + ) + verifyNoMoreInteractions(mockInternalLogger) + verifyNoInteractions(mockWriter) + } + + // endregion + private fun resolveExpectedTimestamp(timestamp: Long): Long { return timestamp + fakeTime.serverTimeOffsetMs } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index b0fb1f7964..14ea1d9668 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -18,6 +18,7 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.utils.loggableStackTrace +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource @@ -44,6 +45,7 @@ import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.config.GlobalRumMonitorTestConfiguration import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.android.rum.utils.verifyApiUsage import com.datadog.android.rum.utils.verifyLog import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension @@ -7028,6 +7030,112 @@ internal class RumViewScopeTest { hasSampleRate(fakeSampleRate) } } + mockInternalLogger.verifyLog( + InternalLogger.Level.DEBUG, + InternalLogger.Target.USER, + RumViewScope.ADDING_VIEW_LOADING_TIME_DEBUG_MESSAGE_FORMAT.format( + expectedViewLoadingTime, + testedScope.key.name + ) + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = false, + noView = false, + noActiveView = false + ), + 15f + ) + verifyNoMoreInteractions(mockWriter) + } + + @Test + fun `M overwrite view loading W handleEvent(AddViewLoadingTime, overwrite=true)`( + forge: Forge + ) { + // Given + val previousLoadingTime = forge.aPositiveLong() + testedScope.viewLoadingTime = previousLoadingTime + val newViewLoadingTime = RumRawEvent.AddViewLoadingTime(overwrite = true) + val expectedViewLoadingTime = newViewLoadingTime.eventTime.nanoTime - fakeEventTime.nanoTime + + // When + testedScope.handleEvent( + newViewLoadingTime, + mockWriter + ) + + // Then + argumentCaptor { + verify(mockWriter) + .write(eq(mockEventBatchWriter), capture(), eq(EventType.DEFAULT)) + assertThat(firstValue) + .apply { + hasTimestamp(resolveExpectedTimestamp(fakeEventTime.timestamp)) + hasName(fakeKey.name) + hasUrl(fakeUrl) + hasDurationGreaterThan(1) + hasLoadingType(null) + hasVersion(2) + hasErrorCount(0) + hasResourceCount(0) + hasActionCount(0) + hasFrustrationCount(0) + hasLongTaskCount(0) + hasFrozenFrameCount(0) + hasCpuMetric(null) + hasMemoryMetric(null, null) + hasRefreshRateMetric(null, null) + isActive(true) + isSlowRendered(false) + hasUserInfo(fakeDatadogContext.userInfo) + hasViewId(testedScope.viewId) + hasApplicationId(fakeParentContext.applicationId) + hasSessionId(fakeParentContext.sessionId) + hasUserSession() + hasNoSyntheticsTest() + hasStartReason(fakeParentContext.sessionStartReason) + hasReplay(fakeHasReplay) + hasReplayStats(fakeReplayStats) + hasSource(fakeSourceViewEvent) + hasLoadingTime(expectedViewLoadingTime) + hasDeviceInfo( + fakeDatadogContext.deviceInfo.deviceName, + fakeDatadogContext.deviceInfo.deviceModel, + fakeDatadogContext.deviceInfo.deviceBrand, + fakeDatadogContext.deviceInfo.deviceType.toViewSchemaType(), + fakeDatadogContext.deviceInfo.architecture + ) + hasOsInfo( + fakeDatadogContext.deviceInfo.osName, + fakeDatadogContext.deviceInfo.osVersion, + fakeDatadogContext.deviceInfo.osMajorVersion + ) + hasConnectivityInfo(fakeDatadogContext.networkInfo) + hasServiceName(fakeDatadogContext.service) + hasVersion(fakeDatadogContext.version) + hasSessionActive(fakeParentContext.isSessionActive) + hasSampleRate(fakeSampleRate) + } + } + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + RumViewScope.OVERWRITING_VIEW_LOADING_TIME_WARNING_MESSAGE_FORMAT.format( + Locale.US, + testedScope.key.name, + previousLoadingTime, + expectedViewLoadingTime + ) + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + noActiveView = false, + noView = false, + overwrite = true + ), + 15f + ) verifyNoMoreInteractions(mockWriter) } @@ -7190,6 +7298,19 @@ internal class RumViewScopeTest { // Then assertThat(testedScope.viewLoadingTime).isNull() + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + RumViewScope.NO_ACTIVE_VIEW_FOR_LOADING_TIME_WARNING_MESSAGE + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + noActiveView = true, + noView = false, + overwrite = fakeOverwrite + ), + 15f + ) verifyNoInteractions(mockWriter) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt index 32d736ab6a..3992b4a0da 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt @@ -9,6 +9,8 @@ package com.datadog.android.rum.utils import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import com.datadog.android.rum.utils.assertj.InternalApiUsageEventAssert import org.assertj.core.api.Assertions.assertThat import org.mockito.ArgumentMatchers.isA import org.mockito.kotlin.argumentCaptor @@ -18,6 +20,17 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.verification.VerificationMode +fun InternalLogger.verifyApiUsage( + apiUsage: InternalTelemetryEvent.ApiUsage, + samplingRate: Float +) { + argumentCaptor { + verify(this@verifyApiUsage).logApiUsage(capture(), eq(samplingRate)) + val event = firstValue + InternalApiUsageEventAssert.assertThat(event).isEqualTo(apiUsage) + } +} + fun InternalLogger.verifyLog( level: InternalLogger.Level, target: InternalLogger.Target, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt new file mode 100644 index 0000000000..66c1a1e3a6 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt @@ -0,0 +1,73 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.utils.assertj + +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import org.assertj.core.api.AbstractAssert +import org.assertj.core.api.Assertions.assertThat + +internal class InternalAddViewLoadingTimeEventAssert(actual: InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) : + AbstractAssert( + actual, + InternalAddViewLoadingTimeEventAssert::class.java + ) { + + fun isEqualTo(expected: InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) { + hasNoView(expected.noView) + hasNoActiveView(expected.noActiveView) + hasOverwrite(expected.overwrite) + hasAdditionalProperties(expected.additionalProperties) + } + + fun hasNoView(expected: Boolean): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.noView) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to" + + " have noView $expected but was ${actual.noView}" + ) + .isEqualTo(expected) + return this + } + + fun hasNoActiveView(expected: Boolean): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.noActiveView) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to have" + + " noActiveView $expected but was ${actual.noActiveView}" + ) + .isEqualTo(expected) + return this + } + + fun hasOverwrite(expected: Boolean): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.overwrite) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to have" + + " overwrite $expected but was ${actual.overwrite}" + ) + .isEqualTo(expected) + return this + } + + fun hasAdditionalProperties(expected: Map): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.additionalProperties) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to have" + + " additionalProperties $expected but was ${actual.additionalProperties}" + ) + .isEqualTo(expected) + return this + } + + companion object { + fun assertThat( + actual: InternalTelemetryEvent.ApiUsage.AddViewLoadingTime + ): InternalAddViewLoadingTimeEventAssert { + return InternalAddViewLoadingTimeEventAssert(actual) + } + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt new file mode 100644 index 0000000000..5c28a0d6ad --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt @@ -0,0 +1,38 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.utils.assertj + +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import org.assertj.core.api.AbstractAssert + +class InternalApiUsageEventAssert(actual: InternalTelemetryEvent.ApiUsage) : + AbstractAssert( + actual, + InternalApiUsageEventAssert::class.java + ) { + + fun isEqualTo(expected: InternalTelemetryEvent.ApiUsage): InternalApiUsageEventAssert { + when (actual) { + is InternalTelemetryEvent.ApiUsage.AddViewLoadingTime -> { + InternalAddViewLoadingTimeEventAssert + .assertThat(actual as InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) + .isEqualTo(expected as InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) + } + + else -> { + failWithMessage("Unknown event type: ${actual::class.java.simpleName}") + } + } + return this + } + + companion object { + fun assertThat(actual: InternalTelemetryEvent.ApiUsage): InternalApiUsageEventAssert { + return InternalApiUsageEventAssert(actual) + } + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index ee88da6010..f9817786ea 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -661,20 +661,6 @@ internal class TelemetryEventHandlerTest { rawEvent.eventTime.timestamp ) } - - is TelemetryConfigurationEvent -> { - assertConfigEventMatchesInternalEvent( - capturedValue, - internalTelemetryEvent as InternalTelemetryEvent.Configuration, - fakeRumContext, - rawEvent.eventTime.timestamp - ) - } - - is InternalTelemetryEvent.InterceptorInstantiated -> { - assertThat(capturedValue).isEqualTo(InternalTelemetryEvent.InterceptorInstantiated) - } - else -> throw IllegalArgumentException( "Unexpected type=${lastValue::class.jvmName} of the captured value." ) @@ -884,7 +870,7 @@ internal class TelemetryEventHandlerTest { .hasSessionId(rumContext.sessionId) .hasViewId(rumContext.viewId) .hasActionId(rumContext.actionId) - .hasAdditionalProperties(internalUsageEvent.additionalProperties ?: emptyMap()) + .hasAdditionalProperties(internalUsageEvent.additionalProperties) .hasDeviceArchitecture(fakeDeviceArchitecture) .hasDeviceBrand(fakeDeviceBrand) .hasDeviceModel(fakeDeviceModel) From 29b64be33d7b5ae6282a89641f5cd695f34987b8 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Wed, 18 Sep 2024 15:24:50 +0200 Subject: [PATCH 035/111] RUM-6035 Add the integration tests related with RumMonitor#addViewLoadingTime API --- .../rum/integration/ManualTrackingRumTest.kt | 220 ++++++++++++++++++ .../tests/assertj/RumEventAssert.kt | 15 ++ 2 files changed, 235 insertions(+) diff --git a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt index e330008a7a..c13f21219c 100644 --- a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt +++ b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/ManualTrackingRumTest.kt @@ -8,6 +8,7 @@ package com.datadog.android.rum.integration import com.datadog.android.api.feature.Feature import com.datadog.android.core.stub.StubSDKCore +import com.datadog.android.rum.ExperimentalRumApi import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.Rum import com.datadog.android.rum.RumActionType @@ -23,6 +24,7 @@ import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension import com.datadog.tools.unit.extensions.config.TestConfiguration import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.LongForgery @@ -30,14 +32,17 @@ import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.data.Offset import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.RepeatedTest +import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.quality.Strictness import java.net.URL +import java.util.concurrent.TimeUnit @Extensions( ExtendWith(MockitoExtension::class), @@ -395,6 +400,221 @@ class ManualTrackingRumTest { // endregion + // region AddViewLoading time + + @OptIn(ExperimentalRumApi::class) + @Test + fun `M attach view loading time W addViewLoadingTime { active view available }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + val startTime = System.nanoTime() + rumMonitor.startView(key, name, emptyMap()) + + // When + val endTime = System.nanoTime() + val expectedViewLoadingTime = endTime - startTime + rumMonitor.addViewLoadingTime(overwrite) + + // Then + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(2) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + doesNotHaveViewLoadingTime() + } + .hasRumEvent(index = 1) { + // view updated with loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + } + + @OptIn(ExperimentalRumApi::class) + @Test + fun `M not attach view loading time W addViewLoadingTime { view was stopped }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + rumMonitor.startView(key, name, emptyMap()) + rumMonitor.stopView(key, emptyMap()) + + // When + rumMonitor.addViewLoadingTime(overwrite) + + // Then + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(2) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + doesNotHaveViewLoadingTime() + } + .hasRumEvent(index = 1) { + // view stopped + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + doesNotHaveViewLoadingTime() + } + } + + @OptIn(ExperimentalRumApi::class) + @Test + fun `M renew view loading time W addViewLoadingTime { loading time was already added, overwrite is true }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + val startTime = System.nanoTime() + rumMonitor.startView(key, name, emptyMap()) + val intermediateTime = System.nanoTime() + rumMonitor.addViewLoadingTime(overwrite) + + // When + Thread.sleep(100) + val endTime = System.nanoTime() + rumMonitor.addViewLoadingTime(true) + + // Then + val expectedFirstViewLoadingTime = intermediateTime - startTime + val expectedSecondViewLoadingTime = endTime - startTime + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(3) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + doesNotHaveViewLoadingTime() + } + .hasRumEvent(index = 1) { + // first view loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedFirstViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + .hasRumEvent(index = 2) { + // second view loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedSecondViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + } + + @OptIn(ExperimentalRumApi::class) + @Test + fun `M not renew view loading time W addViewLoadingTime { loading time was already added, overwrite is false }`( + @StringForgery key: String, + @StringForgery name: String, + @BoolForgery overwrite: Boolean + ) { + // Given + val rumMonitor = GlobalRumMonitor.get(stubSdkCore) + val startTime = System.nanoTime() + rumMonitor.startView(key, name, emptyMap()) + val intermediateTime = System.nanoTime() + rumMonitor.addViewLoadingTime(overwrite) + + // When + Thread.sleep(100) + rumMonitor.addViewLoadingTime(false) + + // Then + val expectedViewLoadingTime = intermediateTime - startTime + val eventsWritten = stubSdkCore.eventsWritten(Feature.RUM_FEATURE_NAME) + StubEventsAssert.assertThat(eventsWritten) + .hasSize(2) + .hasRumEvent(index = 0) { + // Initial view + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasActionCount(0) + doesNotHaveViewLoadingTime() + } + .hasRumEvent(index = 1) { + // first view loading time + hasService(stubSdkCore.getDatadogContext().service) + hasApplicationId(fakeApplicationId) + hasSessionType("user") + hasSource("android") + hasType("view") + hasViewUrl(key) + hasViewName(name) + hasViewLoadingTime( + expectedViewLoadingTime, + offset = Offset.offset(TimeUnit.MILLISECONDS.toNanos(5)) + ) + } + } + + // endregion + companion object { private val mainLooper = MainLooperTestConfiguration() diff --git a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt index c4b72fe574..07594317af 100644 --- a/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt +++ b/reliability/single-fit/rum/src/test/kotlin/com/datadog/android/rum/integration/tests/assertj/RumEventAssert.kt @@ -8,6 +8,7 @@ package com.datadog.android.rum.integration.tests.assertj import com.datadog.tools.unit.assertj.JsonObjectAssert import com.google.gson.JsonObject +import org.assertj.core.data.Offset class RumEventAssert(actual: JsonObject) : JsonObjectAssert(actual, true) { @@ -123,6 +124,20 @@ class RumEventAssert(actual: JsonObject) : // endregion + // region view loading time + + fun hasViewLoadingTime(time: Long, offset: Offset): RumEventAssert { + hasField("view.loading_time", time, offset = offset) + return this + } + + fun doesNotHaveViewLoadingTime(): RumEventAssert { + doesNotHaveField("view.loading_time") + return this + } + + // endregion + companion object { fun assertThat(actual: JsonObject): RumEventAssert { return RumEventAssert(actual) From 743ce9ae71fad0d6450c3e2b7477e18923b8df66 Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Thu, 19 Sep 2024 10:11:24 +0200 Subject: [PATCH 036/111] Update AGP to 8.6.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a80425456d..b309c44dad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ okHttp = "4.12.0" kronosNTP = "0.0.1-alpha11" # Android -androidToolsPlugin = "8.5.2" +androidToolsPlugin = "8.6.1" androidDesugaringSdk = "2.0.4" androidXAnnotations = "1.1.0" androidXAppCompat = "1.3.0" From 23e11c239be8a79fd8b1f7165dc167d915874119 Mon Sep 17 00:00:00 2001 From: Marius Constantin Date: Tue, 17 Sep 2024 15:58:06 +0200 Subject: [PATCH 037/111] RUM-6033 Add telemetry and logs related with RumMonitor#addViewLoadingTime API --- dd-sdk-android-core/api/apiSurface | 2 +- .../api/dd-sdk-android-core.api | 1 + .../com/datadog/android/api/InternalLogger.kt | 4 +- .../android/rum/internal/RumFeature.kt | 2 +- .../domain/event/RumEventSerializer.kt | 4 + .../domain/scope/RumViewManagerScope.kt | 20 ++- .../rum/internal/domain/scope/RumViewScope.kt | 87 ++++++++++--- .../internal/TelemetryEventHandler.kt | 2 +- .../domain/event/RumEventSerializerTest.kt | 110 ++++++++++++++++ .../internal/domain/scope/RumRawEventExt.kt | 10 +- .../domain/scope/RumViewManagerScopeTest.kt | 36 ++++++ .../internal/domain/scope/RumViewScopeTest.kt | 121 ++++++++++++++++++ .../android/rum/utils/InternalLoggerUtils.kt | 13 ++ .../InternalAddViewLoadingTimeEventAssert.kt | 73 +++++++++++ .../assertj/InternalApiUsageEventAssert.kt | 38 ++++++ .../internal/TelemetryEventHandlerTest.kt | 16 +-- 16 files changed, 501 insertions(+), 38 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt diff --git a/dd-sdk-android-core/api/apiSurface b/dd-sdk-android-core/api/apiSurface index 669c3cd39a..a154fffe35 100644 --- a/dd-sdk-android-core/api/apiSurface +++ b/dd-sdk-android-core/api/apiSurface @@ -44,7 +44,7 @@ interface com.datadog.android.api.InternalLogger fun log(Level, List, () -> String, Throwable? = null, Boolean = false, Map? = null) fun logMetric(() -> String, Map, Float) fun startPerformanceMeasure(String, com.datadog.android.core.metrics.TelemetryMetricType, Float, String): com.datadog.android.core.metrics.PerformanceMetric? - fun logApiUsage(com.datadog.android.internal.telemetry.InternalTelemetryEvent.ApiUsage, Float) + fun logApiUsage(com.datadog.android.internal.telemetry.InternalTelemetryEvent.ApiUsage, Float = 15f) companion object val UNBOUND: InternalLogger interface com.datadog.android.api.SdkCore diff --git a/dd-sdk-android-core/api/dd-sdk-android-core.api b/dd-sdk-android-core/api/dd-sdk-android-core.api index 1795e51ba9..b0ee781a8e 100644 --- a/dd-sdk-android-core/api/dd-sdk-android-core.api +++ b/dd-sdk-android-core/api/dd-sdk-android-core.api @@ -89,6 +89,7 @@ public final class com/datadog/android/api/InternalLogger$Companion { public final class com/datadog/android/api/InternalLogger$DefaultImpls { public static synthetic fun log$default (Lcom/datadog/android/api/InternalLogger;Lcom/datadog/android/api/InternalLogger$Level;Lcom/datadog/android/api/InternalLogger$Target;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;ILjava/lang/Object;)V public static synthetic fun log$default (Lcom/datadog/android/api/InternalLogger;Lcom/datadog/android/api/InternalLogger$Level;Ljava/util/List;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;ZLjava/util/Map;ILjava/lang/Object;)V + public static synthetic fun logApiUsage$default (Lcom/datadog/android/api/InternalLogger;Lcom/datadog/android/internal/telemetry/InternalTelemetryEvent$ApiUsage;FILjava/lang/Object;)V } public final class com/datadog/android/api/InternalLogger$Level : java/lang/Enum { diff --git a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt index f49d4c8229..aa848f1a72 100644 --- a/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt +++ b/dd-sdk-android-core/src/main/kotlin/com/datadog/android/api/InternalLogger.kt @@ -137,12 +137,12 @@ interface InternalLogger { * Logs an API usage from the internal implementation. * @param apiUsageEvent the API event being tracked * @param samplingRate value between 0-100 for sampling the event. Note that the sampling rate applied to this - * event will be applied in addition to the global telemetry sampling rate. + * event will be applied in addition to the global telemetry sampling rate. By default, the sampling rate is 15%. */ @InternalApi fun logApiUsage( apiUsageEvent: InternalTelemetryEvent.ApiUsage, - samplingRate: Float + samplingRate: Float = 15f ) companion object { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt index 0e64b9c093..c23b1a0320 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt @@ -540,7 +540,7 @@ internal class RumFeature( internal const val ALL_IN_SAMPLE_RATE: Float = 100f internal const val DEFAULT_SAMPLE_RATE: Float = 100f internal const val DEFAULT_TELEMETRY_SAMPLE_RATE: Float = 20f - internal const val DEFAULT_TELEMETRY_CONFIGURATION_SAMPLE_RATE: Float = 100f + internal const val DEFAULT_TELEMETRY_CONFIGURATION_SAMPLE_RATE: Float = 20f internal const val DEFAULT_LONG_TASK_THRESHOLD_MS = 100L internal const val DD_TELEMETRY_CONFIG_SAMPLE_RATE_TAG = "_dd.telemetry.configuration_sample_rate" diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt index 1b63efac8c..f93fa94670 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializer.kt @@ -20,6 +20,7 @@ import com.datadog.android.rum.model.ViewEvent import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import com.google.gson.JsonObject internal class RumEventSerializer( @@ -55,6 +56,9 @@ internal class RumEventSerializer( is TelemetryConfigurationEvent -> { model.toJson().toString() } + is TelemetryUsageEvent -> { + model.toJson().toString() + } is JsonObject -> { model.toString() } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt index 881682b67c..ace9fbea48 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt @@ -13,6 +13,7 @@ import com.datadog.android.api.storage.DataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.metrics.MethodCallSamplingRate +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.internal.anr.ANRException import com.datadog.android.rum.internal.domain.RumContext @@ -144,7 +145,24 @@ internal class RumViewManagerScope( val importanceForeground = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND val isForegroundProcess = processFlag == importanceForeground - if (applicationDisplayed || !isForegroundProcess) { + if (event is RumRawEvent.AddViewLoadingTime) { + val internalLogger = sdkCore.internalLogger + internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { MESSAGE_MISSING_VIEW } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = event.overwrite, + noView = true, + noActiveView = false + ) + ) + // we should return here and not add the event to the session ended metric missed events as we already + // send the API usage telemetry + return + } else if (applicationDisplayed || !isForegroundProcess) { handleBackgroundEvent(event, writer) } else { val isSilentOrphanEvent = event.javaClass in silentOrphanEventTypes diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt index 88624605d9..eb6669a3db 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt @@ -241,24 +241,74 @@ internal open class RumViewScope( @WorkerThread private fun onAddViewLoadingTime(event: RumRawEvent.AddViewLoadingTime, writer: DataWriter) { - if (stopped) return - val canAddViewLoadingTime = event.overwrite || viewLoadingTime == null - sdkCore.internalLogger.logApiUsage( - InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( - overwrite = event.overwrite, - noView = viewId.isEmpty(), - noActiveView = !isActive() - ), - 100.0f - ) - if (canAddViewLoadingTime) { - viewLoadingTime = event.eventTime.nanoTime - startedNanos - sendViewUpdate(event, writer) - } else { - // TODO RUM-6031 Add logs and telemetry here + val internalLogger = sdkCore.internalLogger + val canUpdateViewLoadingTime = !stopped && (viewLoadingTime == null || event.overwrite) + if (stopped) { + internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { NO_ACTIVE_VIEW_FOR_LOADING_TIME_WARNING_MESSAGE } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = event.overwrite, + noView = false, + noActiveView = true + ) + ) + } + + if (canUpdateViewLoadingTime) { + updateViewLoadingTime(event, internalLogger, writer) } } + private fun updateViewLoadingTime( + event: RumRawEvent.AddViewLoadingTime, + internalLogger: InternalLogger, + writer: DataWriter + ) { + val viewName = key.name + val previousViewLoadingTime = viewLoadingTime + val newLoadingTime = event.eventTime.nanoTime - startedNanos + if (previousViewLoadingTime == null) { + internalLogger.log( + InternalLogger.Level.DEBUG, + InternalLogger.Target.USER, + { ADDING_VIEW_LOADING_TIME_DEBUG_MESSAGE_FORMAT.format(Locale.US, viewLoadingTime, viewName) } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = false, + noView = false, + noActiveView = false + ) + ) + } else if (event.overwrite) { + internalLogger.log( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + { + OVERWRITING_VIEW_LOADING_TIME_WARNING_MESSAGE_FORMAT.format( + Locale.US, + viewName, + previousViewLoadingTime, + newLoadingTime + ) + } + ) + internalLogger.logApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = true, + noView = false, + noActiveView = false + ) + ) + } + viewLoadingTime = newLoadingTime + sendViewUpdate(event, writer) + } + @WorkerThread private fun onStartView( event: RumRawEvent.StartView, @@ -1258,6 +1308,13 @@ internal open class RumViewScope( internal const val SLOW_RENDERED_THRESHOLD_FPS = 55 internal const val NEGATIVE_DURATION_WARNING_MESSAGE = "The computed duration for the " + "view: %s was 0 or negative. In order to keep the view we forced it to 1ns." + internal const val NO_ACTIVE_VIEW_FOR_LOADING_TIME_WARNING_MESSAGE = + "No active view found to add the loading time." + internal const val ADDING_VIEW_LOADING_TIME_DEBUG_MESSAGE_FORMAT = + "View loading time %dns added to the view %s" + internal const val OVERWRITING_VIEW_LOADING_TIME_WARNING_MESSAGE_FORMAT = + "View loading time already exists for the view %s. Replacing the existing %d ns " + + "view loading time with the new %d ns loading time." internal fun fromEvent( parentScope: RumScope, diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt index 6db598a351..007f69ced8 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandler.kt @@ -446,7 +446,7 @@ internal class TelemetryEventHandler( companion object { const val MAX_EVENTS_PER_SESSION = 100 - const val DEFAULT_CONFIGURATION_SAMPLE_RATE = 100f + const val DEFAULT_CONFIGURATION_SAMPLE_RATE = 20f const val ALREADY_SEEN_EVENT_MESSAGE = "Already seen telemetry event with identity=%s, rejecting." const val MAX_EVENT_NUMBER_REACHED_MESSAGE = diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt index dfe4188240..019227bc17 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/event/RumEventSerializerTest.kt @@ -19,6 +19,7 @@ import com.datadog.android.rum.utils.forge.Configurator import com.datadog.android.telemetry.model.TelemetryConfigurationEvent import com.datadog.android.telemetry.model.TelemetryDebugEvent import com.datadog.android.telemetry.model.TelemetryErrorEvent +import com.datadog.android.telemetry.model.TelemetryUsageEvent import com.datadog.tools.unit.assertj.JsonObjectAssert.Companion.assertThat import com.datadog.tools.unit.forge.anException import com.google.gson.JsonObject @@ -33,6 +34,7 @@ import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.api.fail import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings @@ -748,6 +750,110 @@ internal class RumEventSerializerTest { } } + @RepeatedTest(8) + fun `M serialize RUM event W serialize() with TelemetryUsageEvent`( + @Forgery event: TelemetryUsageEvent + ) { + val serialized = testedSerializer.serialize(event) + val jsonObject = JsonParser.parseString(serialized).asJsonObject + assertThat(jsonObject) + .hasField("type", "telemetry") + .hasField("_dd") { + hasField("format_version", 2L) + } + .hasField("date", event.date) + .hasField("source", event.source.name.lowercase(Locale.US).replace('_', '-')) + .hasField("service", event.service) + .hasField("version", event.version) + .hasField("telemetry") { + hasField("usage") { + when (event.telemetry.usage) { + is TelemetryUsageEvent.Usage.AddViewLoadingTime -> { + val usage = event.telemetry.usage as TelemetryUsageEvent.Usage.AddViewLoadingTime + hasField("no_view", usage.noView) + hasField("no_active_view", usage.noActiveView) + hasField("overwritten", usage.overwritten) + } + + else -> { + fail("Usage type not covered in assertions") + } + } + } + if (event.telemetry.device != null) { + hasField("device") { + val device = event.telemetry.device + checkNotNull(device) + if (device.architecture != null) { + hasField("architecture", device.architecture!!) + } + if (device.brand != null) { + hasField("brand", device.brand!!) + } + if (device.model != null) { + hasField("model", device.model!!) + } + } + } + if (event.telemetry.os != null) { + hasField("os") { + val os = event.telemetry.os + checkNotNull(os) + if (os.build != null) { + hasField("build", os.build!!) + } + if (os.name != null) { + hasField("name", os.name!!) + } + if (os.version != null) { + hasField("version", os.version!!) + } + } + } + containsAttributes(event.telemetry.additionalProperties) + } + + val application = event.application + if (application != null) { + assertThat(jsonObject) + .hasField("application") { + hasField("id", application.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("application") + } + + val session = event.session + if (session != null) { + assertThat(jsonObject) + .hasField("session") { + hasField("id", session.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("session") + } + + val view = event.view + if (view != null) { + assertThat(jsonObject) + .hasField("view") { + hasField("id", view.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("view") + } + + val action = event.action + if (action != null) { + assertThat(jsonObject) + .hasField("action") { + hasField("id", action.id) + } + } else { + assertThat(jsonObject).doesNotHaveField("action") + } + } + @Test fun `M serialize RUM event W serialize() with unknown event`( @Forgery unknownEvent: UserInfo @@ -1451,18 +1557,21 @@ internal class RumEventSerializerTest { usr = (it.usr ?: ViewEvent.Usr()).copy(additionalProperties = userAttributes) ) } + 2 -> this.getForgery(ActionEvent::class.java).let { it.copy( context = ActionEvent.Context(additionalProperties = attributes), usr = (it.usr ?: ActionEvent.Usr()).copy(additionalProperties = userAttributes) ) } + 3 -> this.getForgery(ErrorEvent::class.java).let { it.copy( context = ErrorEvent.Context(additionalProperties = attributes), usr = (it.usr ?: ErrorEvent.Usr()).copy(additionalProperties = userAttributes) ) } + 4 -> this.getForgery(ResourceEvent::class.java).let { it.copy( context = ResourceEvent.Context(additionalProperties = attributes), @@ -1470,6 +1579,7 @@ internal class RumEventSerializerTest { .copy(additionalProperties = userAttributes) ) } + else -> this.getForgery(LongTaskEvent::class.java).let { it.copy( context = LongTaskEvent.Context(additionalProperties = attributes), diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt index 22a669d234..1db369cb73 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt @@ -112,6 +112,10 @@ internal fun Forge.addErrorEvent(): RumRawEvent.AddError { ) } +internal fun Forge.addViewLoadingTimeEvent(): RumRawEvent.AddViewLoadingTime { + return RumRawEvent.AddViewLoadingTime(overwrite = aBool()) +} + internal fun Forge.addLongTaskEvent(): RumRawEvent.AddLongTask { return RumRawEvent.AddLongTask( durationNs = aLong(min = 1), @@ -178,7 +182,8 @@ internal fun Forge.invalidBackgroundEvent(): RumRawEvent { stopActionEvent(), stopResourceEvent(), stopResourceWithErrorEvent(), - stopResourceWithStacktraceEvent() + stopResourceWithStacktraceEvent(), + addViewLoadingTimeEvent() ) ) } @@ -197,7 +202,8 @@ internal fun Forge.anyRumEvent(excluding: List = listOf()): RumRawEvent { addLongTaskEvent(), addFeatureFlagEvaluationEvent(), addCustomTimingEvent(), - updatePerformanceMetricEvent() + updatePerformanceMetricEvent(), + addViewLoadingTimeEvent() ) return this.anElementFrom( allEvents.filter { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt index 3b6a0f7452..1f67cea660 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt @@ -17,6 +17,7 @@ import com.datadog.android.api.storage.EventBatchWriter import com.datadog.android.api.storage.EventType import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.RumErrorSource import com.datadog.android.rum.internal.anr.ANRDetectorRunnable @@ -28,6 +29,7 @@ import com.datadog.android.rum.internal.vitals.NoOpVitalMonitor import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.model.ActionEvent import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.android.rum.utils.verifyApiUsage import com.datadog.android.rum.utils.verifyLog import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery @@ -55,6 +57,7 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.util.concurrent.TimeUnit @@ -818,6 +821,39 @@ internal class RumViewManagerScopeTest { // endregion + // region AddViewLoadingTime + + @Test + fun `M send a warning log and api usage telemetry W handleEvent { AddViewLoadingTime, no active view }`( + forge: Forge + ) { + // Given + val fakeEvent = forge.addViewLoadingTimeEvent() + testedScope.applicationDisplayed = true + + // When + testedScope.handleEvent(fakeEvent, mockWriter) + + // Then + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + RumViewManagerScope.MESSAGE_MISSING_VIEW + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = fakeEvent.overwrite, + noView = true, + noActiveView = false + ), + 15f + ) + verifyNoMoreInteractions(mockInternalLogger) + verifyNoInteractions(mockWriter) + } + + // endregion + private fun resolveExpectedTimestamp(timestamp: Long): Long { return timestamp + fakeTime.serverTimeOffsetMs } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt index b0fb1f7964..14ea1d9668 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt @@ -18,6 +18,7 @@ import com.datadog.android.api.storage.EventType import com.datadog.android.core.feature.event.ThreadDump import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.core.internal.utils.loggableStackTrace +import com.datadog.android.internal.telemetry.InternalTelemetryEvent import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumAttributes import com.datadog.android.rum.RumErrorSource @@ -44,6 +45,7 @@ import com.datadog.android.rum.model.LongTaskEvent import com.datadog.android.rum.model.ViewEvent import com.datadog.android.rum.utils.config.GlobalRumMonitorTestConfiguration import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.android.rum.utils.verifyApiUsage import com.datadog.android.rum.utils.verifyLog import com.datadog.tools.unit.annotations.TestConfigurationsProvider import com.datadog.tools.unit.extensions.TestConfigurationExtension @@ -7028,6 +7030,112 @@ internal class RumViewScopeTest { hasSampleRate(fakeSampleRate) } } + mockInternalLogger.verifyLog( + InternalLogger.Level.DEBUG, + InternalLogger.Target.USER, + RumViewScope.ADDING_VIEW_LOADING_TIME_DEBUG_MESSAGE_FORMAT.format( + expectedViewLoadingTime, + testedScope.key.name + ) + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + overwrite = false, + noView = false, + noActiveView = false + ), + 15f + ) + verifyNoMoreInteractions(mockWriter) + } + + @Test + fun `M overwrite view loading W handleEvent(AddViewLoadingTime, overwrite=true)`( + forge: Forge + ) { + // Given + val previousLoadingTime = forge.aPositiveLong() + testedScope.viewLoadingTime = previousLoadingTime + val newViewLoadingTime = RumRawEvent.AddViewLoadingTime(overwrite = true) + val expectedViewLoadingTime = newViewLoadingTime.eventTime.nanoTime - fakeEventTime.nanoTime + + // When + testedScope.handleEvent( + newViewLoadingTime, + mockWriter + ) + + // Then + argumentCaptor { + verify(mockWriter) + .write(eq(mockEventBatchWriter), capture(), eq(EventType.DEFAULT)) + assertThat(firstValue) + .apply { + hasTimestamp(resolveExpectedTimestamp(fakeEventTime.timestamp)) + hasName(fakeKey.name) + hasUrl(fakeUrl) + hasDurationGreaterThan(1) + hasLoadingType(null) + hasVersion(2) + hasErrorCount(0) + hasResourceCount(0) + hasActionCount(0) + hasFrustrationCount(0) + hasLongTaskCount(0) + hasFrozenFrameCount(0) + hasCpuMetric(null) + hasMemoryMetric(null, null) + hasRefreshRateMetric(null, null) + isActive(true) + isSlowRendered(false) + hasUserInfo(fakeDatadogContext.userInfo) + hasViewId(testedScope.viewId) + hasApplicationId(fakeParentContext.applicationId) + hasSessionId(fakeParentContext.sessionId) + hasUserSession() + hasNoSyntheticsTest() + hasStartReason(fakeParentContext.sessionStartReason) + hasReplay(fakeHasReplay) + hasReplayStats(fakeReplayStats) + hasSource(fakeSourceViewEvent) + hasLoadingTime(expectedViewLoadingTime) + hasDeviceInfo( + fakeDatadogContext.deviceInfo.deviceName, + fakeDatadogContext.deviceInfo.deviceModel, + fakeDatadogContext.deviceInfo.deviceBrand, + fakeDatadogContext.deviceInfo.deviceType.toViewSchemaType(), + fakeDatadogContext.deviceInfo.architecture + ) + hasOsInfo( + fakeDatadogContext.deviceInfo.osName, + fakeDatadogContext.deviceInfo.osVersion, + fakeDatadogContext.deviceInfo.osMajorVersion + ) + hasConnectivityInfo(fakeDatadogContext.networkInfo) + hasServiceName(fakeDatadogContext.service) + hasVersion(fakeDatadogContext.version) + hasSessionActive(fakeParentContext.isSessionActive) + hasSampleRate(fakeSampleRate) + } + } + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + RumViewScope.OVERWRITING_VIEW_LOADING_TIME_WARNING_MESSAGE_FORMAT.format( + Locale.US, + testedScope.key.name, + previousLoadingTime, + expectedViewLoadingTime + ) + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + noActiveView = false, + noView = false, + overwrite = true + ), + 15f + ) verifyNoMoreInteractions(mockWriter) } @@ -7190,6 +7298,19 @@ internal class RumViewScopeTest { // Then assertThat(testedScope.viewLoadingTime).isNull() + mockInternalLogger.verifyLog( + InternalLogger.Level.WARN, + InternalLogger.Target.USER, + RumViewScope.NO_ACTIVE_VIEW_FOR_LOADING_TIME_WARNING_MESSAGE + ) + mockInternalLogger.verifyApiUsage( + InternalTelemetryEvent.ApiUsage.AddViewLoadingTime( + noActiveView = true, + noView = false, + overwrite = fakeOverwrite + ), + 15f + ) verifyNoInteractions(mockWriter) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt index 32d736ab6a..3992b4a0da 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/InternalLoggerUtils.kt @@ -9,6 +9,8 @@ package com.datadog.android.rum.utils import com.datadog.android.api.InternalLogger +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import com.datadog.android.rum.utils.assertj.InternalApiUsageEventAssert import org.assertj.core.api.Assertions.assertThat import org.mockito.ArgumentMatchers.isA import org.mockito.kotlin.argumentCaptor @@ -18,6 +20,17 @@ import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.verification.VerificationMode +fun InternalLogger.verifyApiUsage( + apiUsage: InternalTelemetryEvent.ApiUsage, + samplingRate: Float +) { + argumentCaptor { + verify(this@verifyApiUsage).logApiUsage(capture(), eq(samplingRate)) + val event = firstValue + InternalApiUsageEventAssert.assertThat(event).isEqualTo(apiUsage) + } +} + fun InternalLogger.verifyLog( level: InternalLogger.Level, target: InternalLogger.Target, diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt new file mode 100644 index 0000000000..66c1a1e3a6 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalAddViewLoadingTimeEventAssert.kt @@ -0,0 +1,73 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.utils.assertj + +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import org.assertj.core.api.AbstractAssert +import org.assertj.core.api.Assertions.assertThat + +internal class InternalAddViewLoadingTimeEventAssert(actual: InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) : + AbstractAssert( + actual, + InternalAddViewLoadingTimeEventAssert::class.java + ) { + + fun isEqualTo(expected: InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) { + hasNoView(expected.noView) + hasNoActiveView(expected.noActiveView) + hasOverwrite(expected.overwrite) + hasAdditionalProperties(expected.additionalProperties) + } + + fun hasNoView(expected: Boolean): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.noView) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to" + + " have noView $expected but was ${actual.noView}" + ) + .isEqualTo(expected) + return this + } + + fun hasNoActiveView(expected: Boolean): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.noActiveView) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to have" + + " noActiveView $expected but was ${actual.noActiveView}" + ) + .isEqualTo(expected) + return this + } + + fun hasOverwrite(expected: Boolean): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.overwrite) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to have" + + " overwrite $expected but was ${actual.overwrite}" + ) + .isEqualTo(expected) + return this + } + + fun hasAdditionalProperties(expected: Map): InternalAddViewLoadingTimeEventAssert { + assertThat(actual.additionalProperties) + .overridingErrorMessage( + "Expected viewLoadingTimeTelemetryEvent event to have" + + " additionalProperties $expected but was ${actual.additionalProperties}" + ) + .isEqualTo(expected) + return this + } + + companion object { + fun assertThat( + actual: InternalTelemetryEvent.ApiUsage.AddViewLoadingTime + ): InternalAddViewLoadingTimeEventAssert { + return InternalAddViewLoadingTimeEventAssert(actual) + } + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt new file mode 100644 index 0000000000..5c28a0d6ad --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/utils/assertj/InternalApiUsageEventAssert.kt @@ -0,0 +1,38 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2016-Present Datadog, Inc. + */ + +package com.datadog.android.rum.utils.assertj + +import com.datadog.android.internal.telemetry.InternalTelemetryEvent +import org.assertj.core.api.AbstractAssert + +class InternalApiUsageEventAssert(actual: InternalTelemetryEvent.ApiUsage) : + AbstractAssert( + actual, + InternalApiUsageEventAssert::class.java + ) { + + fun isEqualTo(expected: InternalTelemetryEvent.ApiUsage): InternalApiUsageEventAssert { + when (actual) { + is InternalTelemetryEvent.ApiUsage.AddViewLoadingTime -> { + InternalAddViewLoadingTimeEventAssert + .assertThat(actual as InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) + .isEqualTo(expected as InternalTelemetryEvent.ApiUsage.AddViewLoadingTime) + } + + else -> { + failWithMessage("Unknown event type: ${actual::class.java.simpleName}") + } + } + return this + } + + companion object { + fun assertThat(actual: InternalTelemetryEvent.ApiUsage): InternalApiUsageEventAssert { + return InternalApiUsageEventAssert(actual) + } + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt index ee88da6010..f9817786ea 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/telemetry/internal/TelemetryEventHandlerTest.kt @@ -661,20 +661,6 @@ internal class TelemetryEventHandlerTest { rawEvent.eventTime.timestamp ) } - - is TelemetryConfigurationEvent -> { - assertConfigEventMatchesInternalEvent( - capturedValue, - internalTelemetryEvent as InternalTelemetryEvent.Configuration, - fakeRumContext, - rawEvent.eventTime.timestamp - ) - } - - is InternalTelemetryEvent.InterceptorInstantiated -> { - assertThat(capturedValue).isEqualTo(InternalTelemetryEvent.InterceptorInstantiated) - } - else -> throw IllegalArgumentException( "Unexpected type=${lastValue::class.jvmName} of the captured value." ) @@ -884,7 +870,7 @@ internal class TelemetryEventHandlerTest { .hasSessionId(rumContext.sessionId) .hasViewId(rumContext.viewId) .hasActionId(rumContext.actionId) - .hasAdditionalProperties(internalUsageEvent.additionalProperties ?: emptyMap()) + .hasAdditionalProperties(internalUsageEvent.additionalProperties) .hasDeviceArchitecture(fakeDeviceArchitecture) .hasDeviceBrand(fakeDeviceBrand) .hasDeviceModel(fakeDeviceModel) From 28a2f8705daabc80cab9aedfe25458b9a2fd747a Mon Sep 17 00:00:00 2001 From: "Xavier F. Gouchet" Date: Thu, 19 Sep 2024 10:09:39 +0200 Subject: [PATCH 038/111] RUM-6235 add server-sent stream to sample app --- gradle/libs.versions.toml | 15 +++- sample/kotlin/build.gradle.kts | 2 + .../android/sample/traces/TracesFragment.kt | 13 +++ .../android/sample/traces/TracesViewModel.kt | 86 +++++++++++++++++-- .../src/main/res/layout/fragment_traces.xml | 12 ++- sample/kotlin/src/main/res/values/strings.xml | 1 + sample/vendor-lib/build.gradle.kts | 1 + .../android/vendor/sample/LocalServer.kt | 67 +++++++++++---- .../datadog/android/vendor/sample/SseEvent.kt | 14 +++ 9 files changed, 184 insertions(+), 27 deletions(-) create mode 100644 sample/vendor-lib/src/main/kotlin/com/datadog/android/vendor/sample/SseEvent.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b309c44dad..944110b48a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -89,7 +89,8 @@ timber = "5.0.1" coroutines = "1.4.2" # Local Server -ktor = "1.6.0" +ktor = "1.6.8" +ktorServer = "3.0.0-rc-1" # Otel jctools = "3.3.0" @@ -233,8 +234,10 @@ coroutinesCore = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", ver # Local Server ktorCore = { module = "io.ktor:ktor", version.ref = "ktor" } -ktorNetty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } ktorGson = { module = "io.ktor:ktor-gson", version.ref = "ktor" } +ktorServerCore = { module = "io.ktor:ktor-server-core", version.ref = "ktorServer" } +ktorServerNetty = { module = "io.ktor:ktor-server-netty", version.ref = "ktorServer" } +ktorServerSSE = { module = "io.ktor:ktor-server-sse", version.ref = "ktorServer" } # Otel jctools = { module = "org.jctools:jctools-core", version.ref = "jctools" } @@ -306,8 +309,12 @@ glide = [ ktor = [ "ktorCore", - "ktorNetty", - "ktorGson" + "ktorGson", +] +ktorServer = [ + "ktorServerCore", + "ktorServerNetty", + "ktorServerSSE" ] traceCore = [ diff --git a/sample/kotlin/build.gradle.kts b/sample/kotlin/build.gradle.kts index c5b3bf17e6..9fab851bff 100644 --- a/sample/kotlin/build.gradle.kts +++ b/sample/kotlin/build.gradle.kts @@ -195,6 +195,7 @@ dependencies { implementation("io.opentracing.contrib:opentracing-rxjava-3:0.1.4") { exclude(group = "io.opentracing") } + implementation("com.launchdarkly:okhttp-eventsource:2.5.0") // Image Loading Library implementation(libs.coil) @@ -220,6 +221,7 @@ dependencies { implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation(libs.okHttp) implementation(libs.gson) + implementation("com.launchdarkly:okhttp-eventsource:2.5.0") // Misc implementation(libs.timber) diff --git a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt index 48997b76a1..076fc7c9d5 100644 --- a/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt +++ b/sample/kotlin/src/main/kotlin/com/datadog/android/sample/traces/TracesFragment.kt @@ -39,6 +39,7 @@ internal class TracesFragment : Fragment(), View.OnClickListener { rootView.findViewById