From 928d6c2b5d60f70e2c35031840e72fbfb844c6a5 Mon Sep 17 00:00:00 2001 From: Aleksandr Gringauz Date: Wed, 15 Oct 2025 17:48:12 +0200 Subject: [PATCH 1/2] RUM-11784: Send TTID vital --- .../kotlin/com/datadog/android/rum/Rum.kt | 12 +- .../android/rum/internal/RumFeature.kt | 2 +- .../domain/scope/RumApplicationScope.kt | 13 +- .../rum/internal/domain/scope/RumEventExt.kt | 11 ++ .../internal/domain/scope/RumSessionScope.kt | 71 +++++++- .../domain/scope/RumViewManagerScope.kt | 12 +- .../rum/internal/domain/scope/RumViewScope.kt | 107 ++---------- .../domain/scope/RumVitalEventHelper.kt | 131 ++++++++++++++ .../rum/internal/monitor/DatadogRumMonitor.kt | 10 +- .../internal/startup/RumAppStartupDetector.kt | 6 +- .../startup/RumAppStartupDetectorImpl.kt | 23 +-- .../internal/startup/RumStartupScenario.kt | 15 +- .../assertj/VitalAppLaunchPropertiesAssert.kt | 67 +++++++ .../android/rum/assertj/VitalEventAssert.kt | 8 + .../domain/event/RumEventSerializerTest.kt | 49 ++++-- ...pplicationScopeAttributePropagationTest.kt | 10 +- .../domain/scope/RumApplicationScopeTest.kt | 11 +- ...RumSessionScopeAttributePropagationTest.kt | 10 +- .../domain/scope/RumSessionScopeTest.kt | 164 +++++++++++++++++- ...iewManagerScopeAttributePropagationTest.kt | 9 +- .../domain/scope/RumViewManagerScopeTest.kt | 26 ++- .../RumViewScopeAttributePropagationTest.kt | 9 +- .../internal/domain/scope/RumViewScopeTest.kt | 9 +- .../internal/monitor/DatadogRumMonitorTest.kt | 42 ++++- .../startup/RumAppStartupDetectorImplTest.kt | 51 ++++-- .../startup/RumFirstDrawTimeReporterTest.kt | 3 +- .../rum/internal/startup/RumTTIDInfoUtils.kt | 8 +- .../tests/elmyr/BatteryInfoForgeryFactory.kt | 20 +++ .../tests/elmyr/DisplayInfoForgeryFactory.kt | 19 ++ .../android/rum/utils/forge/Configurator.kt | 4 + .../utils/forge/VitalEventForgeryFactory.kt | 42 +++-- 31 files changed, 775 insertions(+), 199 deletions(-) create mode 100644 features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumVitalEventHelper.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchPropertiesAssert.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/BatteryInfoForgeryFactory.kt create mode 100644 features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/DisplayInfoForgeryFactory.kt diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt index e1ba33d111..edb69cb5d7 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt @@ -16,8 +16,10 @@ import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.RateBasedSampler +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumAnonymousIdentifierManager import com.datadog.android.rum.internal.RumFeature +import com.datadog.android.rum.internal.domain.scope.RumVitalEventHelper import com.datadog.android.rum.internal.metric.SessionEndedMetricDispatcher import com.datadog.android.rum.internal.monitor.DatadogRumMonitor import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter @@ -145,7 +147,15 @@ object Rum { accessibilitySnapshotManager = rumFeature.accessibilitySnapshotManager, batteryInfoProvider = rumFeature.batteryInfoProvider, displayInfoProvider = rumFeature.displayInfoProvider, - rumAppStartupTelemetryReporter = RumAppStartupTelemetryReporter.create(sdkCore) + rumAppStartupTelemetryReporter = RumAppStartupTelemetryReporter.create(sdkCore), + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = rumFeature.configuration.rumSessionTypeOverride, + batteryInfoProvider = rumFeature.batteryInfoProvider, + displayInfoProvider = rumFeature.displayInfoProvider, + sampleRate = rumFeature.sampleRate, + internalLogger = sdkCore.internalLogger + ), + featuresContextResolver = FeaturesContextResolver() ) } 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 451ab2ef0a..04db33a2b6 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 @@ -687,7 +687,7 @@ internal class RumFeature( override fun onFirstFrameDrawn(timestampNs: Long) { val info = RumTTIDInfo( scenario = scenario, - durationNs = timestampNs - scenario.initialTimeNs + durationNs = timestampNs - scenario.initialTime.nanoTime ) (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) ?.sendTTIDEvent(info) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt index af6fc9d22f..33f1d9f4a9 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt @@ -18,6 +18,7 @@ import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.InfoProvider import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time @@ -52,7 +53,9 @@ internal class RumApplicationScope( private val accessibilitySnapshotManager: AccessibilitySnapshotManager, private val batteryInfoProvider: InfoProvider, private val displayInfoProvider: InfoProvider, - private val rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter + private val rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter, + private val rumVitalEventHelper: RumVitalEventHelper, + private val featuresContextResolver: FeaturesContextResolver ) : RumScope, RumViewChangedListener { override val parentScope: RumScope? = null @@ -81,7 +84,9 @@ internal class RumApplicationScope( accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, - rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, + rumVitalEventHelper = rumVitalEventHelper, + featuresContextResolver = featuresContextResolver ) ) @@ -202,7 +207,9 @@ internal class RumApplicationScope( accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, - rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, + rumVitalEventHelper = rumVitalEventHelper, + featuresContextResolver = featuresContextResolver ) childScopes.add(newSession) if (event !is RumRawEvent.StartView) { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt index 02a8b11fac..611a98184c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumEventExt.kt @@ -17,12 +17,14 @@ import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.featureoperations.FailureReason import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.domain.event.ResourceTiming +import com.datadog.android.rum.internal.startup.RumStartupScenario 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.model.VitalEvent +import com.datadog.android.rum.model.VitalEvent.StartupType import java.util.Locale // region Resource.Method conversion @@ -669,4 +671,13 @@ internal fun FailureReason.toSchemaFailureReason(): VitalEvent.FailureReason { FailureReason.OTHER -> VitalEvent.FailureReason.OTHER } } + +internal fun RumStartupScenario.toVitalStartupType(): StartupType { + return when (this) { + is RumStartupScenario.Cold -> StartupType.COLD_START + is RumStartupScenario.WarmAfterActivityDestroyed, + is RumStartupScenario.WarmFirstActivity -> StartupType.WARM_START + } +} + // endregion diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 52f7a0a5e7..3722d80673 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -16,6 +16,7 @@ import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.InfoProvider import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time @@ -25,10 +26,12 @@ import com.datadog.android.rum.internal.domain.display.DisplayInfo import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter +import com.datadog.android.rum.internal.utils.newRumEventWriteOperation import com.datadog.android.rum.internal.utils.percent import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier +import com.datadog.android.rum.model.VitalEvent import java.security.SecureRandom import java.util.UUID import java.util.concurrent.TimeUnit @@ -58,7 +61,9 @@ internal class RumSessionScope( private val sessionInactivityNanos: Long = DEFAULT_SESSION_INACTIVITY_NS, private val sessionMaxDurationNanos: Long = DEFAULT_SESSION_MAX_DURATION_NS, rumSessionTypeOverride: RumSessionType?, - private val rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter + private val rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter, + private val rumVitalEventHelper: RumVitalEventHelper, + private val featuresContextResolver: FeaturesContextResolver ) : RumScope { internal var sessionId = RumContext.NULL_UUID @@ -95,7 +100,8 @@ internal class RumSessionScope( rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, - displayInfoProvider = displayInfoProvider + displayInfoProvider = displayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) internal val activeView: RumViewScope? @@ -156,11 +162,12 @@ internal class RumSessionScope( when (event) { is RumRawEvent.AppStartTTIDEvent -> { if (sessionState == State.TRACKED) { - rumAppStartupTelemetryReporter.reportTTID( - info = event.info, - indexInSession = appStartIndex + handleTTIDEvent( + event = event, + datadogContext = datadogContext, + writeScope = writeScope, + writer = actualWriter ) - appStartIndex++ } } is RumRawEvent.SdkInit -> {} @@ -281,6 +288,58 @@ internal class RumSessionScope( ) } + private fun handleTTIDEvent( + event: RumRawEvent.AppStartTTIDEvent, + datadogContext: DatadogContext, + writeScope: EventWriteScope, + writer: DataWriter + ) { + sdkCore.newRumEventWriteOperation(datadogContext, writeScope, writer) { + createTTIDVitalEvent( + event = event, + datadogContext = datadogContext + ) + }.submit() + + rumAppStartupTelemetryReporter.reportTTID( + info = event.info, + indexInSession = appStartIndex + ) + + appStartIndex++ + } + + private fun createTTIDVitalEvent( + event: RumRawEvent.AppStartTTIDEvent, + datadogContext: DatadogContext + ): VitalEvent { + val rumContext = getRumContext() + + val hasReplay = childScope?.activeView?.viewId?.let { + featuresContextResolver.resolveViewHasReplay(datadogContext, it) + } + + return rumVitalEventHelper.newVitalEvent( + timestampMs = event.info.scenario.initialTime.timestamp + sdkCore.time.serverTimeOffsetMs, + datadogContext = datadogContext, + eventAttributes = emptyMap(), + customAttributes = getCustomAttributes(), + view = null, + hasReplay = hasReplay, + rumContext = rumContext, + vital = VitalEvent.Vital.AppLaunchProperties( + id = UUID.randomUUID().toString(), + name = null, + description = null, + appLaunchMetric = VitalEvent.AppLaunchMetric.TTID, + duration = event.info.durationNs, + startupType = event.info.scenario.toVitalStartupType(), + isPrewarmed = null, + hasSavedInstanceStateBundle = event.info.scenario.hasSavedInstanceStateBundle + ) + ) + } + // endregion companion object { 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 066404efcc..3a423ab36f 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 @@ -58,7 +58,8 @@ internal class RumViewManagerScope( private val rumSessionTypeOverride: RumSessionType?, private val accessibilitySnapshotManager: AccessibilitySnapshotManager, private val batteryInfoProvider: InfoProvider, - private val displayInfoProvider: InfoProvider + private val displayInfoProvider: InfoProvider, + private val rumVitalEventHelper: RumVitalEventHelper ) : RumScope { private val interactionToNextViewMetricResolver: InteractionToNextViewMetricResolver = @@ -285,7 +286,8 @@ internal class RumViewManagerScope( rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, - displayInfoProvider = displayInfoProvider + displayInfoProvider = displayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) applicationDisplayed = true childrenScopes.add(viewScope) @@ -368,7 +370,8 @@ internal class RumViewManagerScope( rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, - displayInfoProvider = displayInfoProvider + displayInfoProvider = displayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) } @@ -410,7 +413,8 @@ internal class RumViewManagerScope( rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, - displayInfoProvider = displayInfoProvider + displayInfoProvider = displayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) } 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 cea4cc757d..2b0966e630 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 @@ -45,7 +45,6 @@ import com.datadog.android.rum.internal.toAction import com.datadog.android.rum.internal.toError import com.datadog.android.rum.internal.toLongTask import com.datadog.android.rum.internal.toView -import com.datadog.android.rum.internal.toVital import com.datadog.android.rum.internal.utils.buildDDTagsString import com.datadog.android.rum.internal.utils.hasUserData import com.datadog.android.rum.internal.utils.newRumEventWriteOperation @@ -88,7 +87,8 @@ internal open class RumViewScope( private val rumSessionTypeOverride: RumSessionType?, private val accessibilitySnapshotManager: AccessibilitySnapshotManager, private val batteryInfoProvider: InfoProvider, - private val displayInfoProvider: InfoProvider + private val displayInfoProvider: InfoProvider, + private val rumVitalEventHelper: RumVitalEventHelper ) : RumScope { internal val url = key.url.replace('.', '/') @@ -305,102 +305,24 @@ internal open class RumViewScope( eventAttributes: Map ): VitalEvent { val rumContext = getRumContext() - val syntheticsAttribute = if ( - rumContext.syntheticsTestId.isNullOrBlank() || - rumContext.syntheticsResultId.isNullOrBlank() - ) { - null - } else { - VitalEvent.Synthetics( - testId = rumContext.syntheticsTestId, - resultId = rumContext.syntheticsResultId - ) - } + val hasReplay = featuresContextResolver.resolveViewHasReplay( datadogContext, rumContext.viewId.orEmpty() ) - val sessionType = when { - rumSessionTypeOverride != null -> rumSessionTypeOverride.toVital() - syntheticsAttribute == null -> VitalEvent.VitalEventSessionType.USER - else -> VitalEvent.VitalEventSessionType.SYNTHETICS - } - val batteryInfo = batteryInfoProvider.getState() - val displayInfo = displayInfoProvider.getState() - val user = datadogContext.userInfo - - return VitalEvent( - date = event.eventTime.timestamp + serverTimeOffsetInMs, - context = VitalEvent.Context( - additionalProperties = getCustomAttributes().toMutableMap().also { - it.putAll(eventAttributes) - } - ), - dd = VitalEvent.Dd( - session = VitalEvent.DdSession( - sessionPrecondition = rumContext.sessionStartReason.toVitalSessionPrecondition() - ), - configuration = VitalEvent.Configuration(sessionSampleRate = sampleRate) - ), - application = VitalEvent.Application( - id = rumContext.applicationId, - currentLocale = datadogContext.deviceInfo.localeInfo.currentLocale - ), - synthetics = syntheticsAttribute, - session = VitalEvent.VitalEventSession( - id = rumContext.sessionId, - type = sessionType, - hasReplay = hasReplay - ), + return rumVitalEventHelper.newVitalEvent( + timestampMs = event.eventTime.timestamp + serverTimeOffsetInMs, + datadogContext = datadogContext, + eventAttributes = eventAttributes, + customAttributes = getCustomAttributes(), view = VitalEvent.VitalEventView( id = rumContext.viewId.orEmpty(), name = rumContext.viewName, url = rumContext.viewUrl.orEmpty() ), - source = VitalEvent.VitalEventSource.tryFromSource( - source = datadogContext.source, - internalLogger = sdkCore.internalLogger - ), - account = datadogContext.accountInfo?.let { - VitalEvent.Account( - id = it.id, - name = it.name, - additionalProperties = it.extraInfo.toMutableMap() - ) - }, - usr = if (user.hasUserData()) { - VitalEvent.Usr( - id = user.id, - name = user.name, - email = user.email, - anonymousId = user.anonymousId, - additionalProperties = user.additionalProperties.toMutableMap() - ) - } else { - null - }, - device = VitalEvent.Device( - type = datadogContext.deviceInfo.deviceType.toVitalSchemaType(), - name = datadogContext.deviceInfo.deviceName, - model = datadogContext.deviceInfo.deviceModel, - brand = datadogContext.deviceInfo.deviceBrand, - architecture = datadogContext.deviceInfo.architecture, - locales = datadogContext.deviceInfo.localeInfo.locales, - timeZone = datadogContext.deviceInfo.localeInfo.timeZone, - batteryLevel = batteryInfo.batteryLevel, - powerSavingMode = batteryInfo.lowPowerMode, - brightnessLevel = displayInfo.screenBrightness - ), - os = VitalEvent.Os( - name = datadogContext.deviceInfo.osName, - version = datadogContext.deviceInfo.osVersion, - versionMajor = datadogContext.deviceInfo.osMajorVersion - ), - connectivity = datadogContext.networkInfo.toVitalConnectivity(), - version = datadogContext.version, - service = datadogContext.service, - ddtags = buildDDTagsString(datadogContext), + hasReplay = hasReplay, + rumContext = rumContext, vital = VitalEvent.Vital.FeatureOperationProperties( id = UUID.randomUUID().toString(), name = name, @@ -460,7 +382,8 @@ internal open class RumViewScope( rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, - displayInfoProvider = displayInfoProvider + displayInfoProvider = displayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) } @@ -1747,7 +1670,8 @@ internal open class RumViewScope( rumSessionTypeOverride: RumSessionType?, accessibilitySnapshotManager: AccessibilitySnapshotManager, batteryInfoProvider: InfoProvider, - displayInfoProvider: InfoProvider + displayInfoProvider: InfoProvider, + rumVitalEventHelper: RumVitalEventHelper ): RumViewScope { val networkSettledMetricResolver = NetworkSettledMetricResolver( networkSettledResourceIdentifier, @@ -1783,7 +1707,8 @@ internal open class RumViewScope( rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, - displayInfoProvider = displayInfoProvider + displayInfoProvider = displayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumVitalEventHelper.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumVitalEventHelper.kt new file mode 100644 index 0000000000..04e01cc542 --- /dev/null +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumVitalEventHelper.kt @@ -0,0 +1,131 @@ +/* + * 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.internal.domain.scope + +import com.datadog.android.api.InternalLogger +import com.datadog.android.api.context.DatadogContext +import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.domain.InfoProvider +import com.datadog.android.rum.internal.domain.RumContext +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import com.datadog.android.rum.internal.toVital +import com.datadog.android.rum.internal.utils.buildDDTagsString +import com.datadog.android.rum.internal.utils.hasUserData +import com.datadog.android.rum.model.VitalEvent + +internal class RumVitalEventHelper( + private val rumSessionTypeOverride: RumSessionType?, + private val batteryInfoProvider: InfoProvider, + private val displayInfoProvider: InfoProvider, + private val sampleRate: Float, + private val internalLogger: InternalLogger +) { + @Suppress("LongMethod") + fun newVitalEvent( + timestampMs: Long, + datadogContext: DatadogContext, + eventAttributes: Map, + customAttributes: Map, + view: VitalEvent.VitalEventView?, + hasReplay: Boolean?, + rumContext: RumContext, + vital: VitalEvent.Vital + ): VitalEvent { + val syntheticsAttribute = if ( + rumContext.syntheticsTestId.isNullOrBlank() || + rumContext.syntheticsResultId.isNullOrBlank() + ) { + null + } else { + VitalEvent.Synthetics( + testId = rumContext.syntheticsTestId, + resultId = rumContext.syntheticsResultId + ) + } + + val sessionType = when { + rumSessionTypeOverride != null -> rumSessionTypeOverride.toVital() + syntheticsAttribute == null -> VitalEvent.VitalEventSessionType.USER + else -> VitalEvent.VitalEventSessionType.SYNTHETICS + } + + val batteryInfo = batteryInfoProvider.getState() + val displayInfo = displayInfoProvider.getState() + val user = datadogContext.userInfo + + return VitalEvent( + date = timestampMs, + context = VitalEvent.Context( + additionalProperties = customAttributes.toMutableMap().also { + it.putAll(eventAttributes) + } + ), + dd = VitalEvent.Dd( + session = VitalEvent.DdSession( + sessionPrecondition = rumContext.sessionStartReason.toVitalSessionPrecondition() + ), + configuration = VitalEvent.Configuration(sessionSampleRate = sampleRate) + ), + application = VitalEvent.Application( + id = rumContext.applicationId, + currentLocale = datadogContext.deviceInfo.localeInfo.currentLocale + ), + synthetics = syntheticsAttribute, + session = VitalEvent.VitalEventSession( + id = rumContext.sessionId, + type = sessionType, + hasReplay = hasReplay + ), + view = view, + source = VitalEvent.VitalEventSource.tryFromSource( + source = datadogContext.source, + internalLogger = internalLogger + ), + account = datadogContext.accountInfo?.let { + VitalEvent.Account( + id = it.id, + name = it.name, + additionalProperties = it.extraInfo.toMutableMap() + ) + }, + usr = if (user.hasUserData()) { + VitalEvent.Usr( + id = user.id, + name = user.name, + email = user.email, + anonymousId = user.anonymousId, + additionalProperties = user.additionalProperties.toMutableMap() + ) + } else { + null + }, + device = VitalEvent.Device( + type = datadogContext.deviceInfo.deviceType.toVitalSchemaType(), + name = datadogContext.deviceInfo.deviceName, + model = datadogContext.deviceInfo.deviceModel, + brand = datadogContext.deviceInfo.deviceBrand, + architecture = datadogContext.deviceInfo.architecture, + locales = datadogContext.deviceInfo.localeInfo.locales, + timeZone = datadogContext.deviceInfo.localeInfo.timeZone, + batteryLevel = batteryInfo.batteryLevel, + powerSavingMode = batteryInfo.lowPowerMode, + brightnessLevel = displayInfo.screenBrightness + ), + os = VitalEvent.Os( + name = datadogContext.deviceInfo.osName, + version = datadogContext.deviceInfo.osVersion, + versionMajor = datadogContext.deviceInfo.osMajorVersion + ), + connectivity = datadogContext.networkInfo.toVitalConnectivity(), + version = datadogContext.version, + service = datadogContext.service, + ddtags = buildDDTagsString(datadogContext), + vital = vital + ) + } +} 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 f370992cf3..edeb9f45f5 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 @@ -40,6 +40,7 @@ import com.datadog.android.rum.RumSessionType import com.datadog.android.rum._RumInternalProxy import com.datadog.android.rum.featureoperations.FailureReason import com.datadog.android.rum.internal.CombinedRumSessionListener +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.debug.RumDebugListener @@ -55,6 +56,7 @@ import com.datadog.android.rum.internal.domain.scope.RumApplicationScope import com.datadog.android.rum.internal.domain.scope.RumRawEvent import com.datadog.android.rum.internal.domain.scope.RumScopeKey import com.datadog.android.rum.internal.domain.scope.RumSessionScope +import com.datadog.android.rum.internal.domain.scope.RumVitalEventHelper import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter @@ -96,7 +98,9 @@ internal class DatadogRumMonitor( accessibilitySnapshotManager: AccessibilitySnapshotManager, batteryInfoProvider: InfoProvider, displayInfoProvider: InfoProvider, - rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter, + rumVitalEventHelper: RumVitalEventHelper, + private val featuresContextResolver: FeaturesContextResolver ) : RumMonitor, AdvancedRumMonitor { internal var rootScope = RumApplicationScope( @@ -118,7 +122,9 @@ internal class DatadogRumMonitor( accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, - rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, + rumVitalEventHelper = rumVitalEventHelper, + featuresContextResolver = featuresContextResolver ) internal val keepAliveRunnable = Runnable { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetector.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetector.kt index f2c875cc24..18fe64200c 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetector.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetector.kt @@ -10,6 +10,8 @@ import android.app.Application import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.system.BuildSdkVersionProvider import com.datadog.android.rum.DdRumContentProvider +import com.datadog.android.rum.internal.domain.Time +import com.datadog.android.rum.internal.domain.asTime internal interface RumAppStartupDetector { interface Listener { @@ -27,9 +29,9 @@ internal interface RumAppStartupDetector { return RumAppStartupDetectorImpl( application = application, buildSdkVersionProvider = BuildSdkVersionProvider.DEFAULT, - appStartupTimeProviderNs = { sdkCore.appStartTimeNs }, + appStartupTimeProvider = { sdkCore.appStartTimeNs.asTime() }, processImportanceProvider = { DdRumContentProvider.processImportance }, - timeProviderNs = { System.nanoTime() }, + timeProvider = { Time() }, listener ) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImpl.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImpl.kt index 9f503b0def..c67a1c1e49 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImpl.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImpl.kt @@ -12,15 +12,16 @@ import android.app.Application import android.os.Build import android.os.Bundle import com.datadog.android.core.internal.system.BuildSdkVersionProvider +import com.datadog.android.rum.internal.domain.Time import java.lang.ref.WeakReference import kotlin.time.Duration.Companion.seconds internal class RumAppStartupDetectorImpl( private val application: Application, private val buildSdkVersionProvider: BuildSdkVersionProvider, - private val appStartupTimeProviderNs: () -> Long, + private val appStartupTimeProvider: () -> Time, private val processImportanceProvider: () -> Int, - private val timeProviderNs: () -> Long, + private val timeProvider: () -> Time, private val listener: RumAppStartupDetector.Listener ) : RumAppStartupDetector, Application.ActivityLifecycleCallbacks { @@ -68,39 +69,39 @@ internal class RumAppStartupDetectorImpl( private fun onBeforeActivityCreated(activity: Activity, savedInstanceState: Bundle?) { numberOfActivities++ - val nowNs = timeProviderNs() + val now = timeProvider() if (numberOfActivities == 1 && !isChangingConfigurations) { - val processStartTimeNs = appStartupTimeProviderNs() + val processStartTime = appStartupTimeProvider() val processStartedInForeground = processImportanceProvider() == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND - val gapNs = nowNs - processStartTimeNs + val gapNs = now.nanoTime - processStartTime.nanoTime val hasSavedInstanceStateBundle = savedInstanceState != null val weakActivity = WeakReference(activity) val scenario = if (isFirstActivityForProcess) { if (!processStartedInForeground || gapNs > START_GAP_THRESHOLD_NS) { RumStartupScenario.WarmFirstActivity( - initialTimeNs = nowNs, hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = weakActivity, - appStartActivityOnCreateGapNs = gapNs + appStartActivityOnCreateGapNs = gapNs, + initialTime = now ) } else { RumStartupScenario.Cold( - initialTimeNs = processStartTimeNs, hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = weakActivity, - appStartActivityOnCreateGapNs = gapNs + appStartActivityOnCreateGapNs = gapNs, + initialTime = processStartTime ) } } else { RumStartupScenario.WarmAfterActivityDestroyed( - initialTimeNs = nowNs, hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, - activity = weakActivity + activity = weakActivity, + initialTime = now ) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumStartupScenario.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumStartupScenario.kt index b20fa19b1f..0ea1c62efc 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumStartupScenario.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/startup/RumStartupScenario.kt @@ -7,31 +7,32 @@ package com.datadog.android.rum.internal.startup import android.app.Activity +import com.datadog.android.rum.internal.domain.Time import java.lang.ref.WeakReference internal sealed interface RumStartupScenario { - val initialTimeNs: Long + val initialTime: Time val hasSavedInstanceStateBundle: Boolean val activity: WeakReference class Cold( - override val initialTimeNs: Long, override val hasSavedInstanceStateBundle: Boolean, override val activity: WeakReference, - val appStartActivityOnCreateGapNs: Long + val appStartActivityOnCreateGapNs: Long, + override val initialTime: Time ) : RumStartupScenario class WarmFirstActivity( - override val initialTimeNs: Long, override val hasSavedInstanceStateBundle: Boolean, override val activity: WeakReference, - val appStartActivityOnCreateGapNs: Long + val appStartActivityOnCreateGapNs: Long, + override val initialTime: Time ) : RumStartupScenario class WarmAfterActivityDestroyed( - override val initialTimeNs: Long, override val hasSavedInstanceStateBundle: Boolean, - override val activity: WeakReference + override val activity: WeakReference, + override val initialTime: Time ) : RumStartupScenario } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchPropertiesAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchPropertiesAssert.kt new file mode 100644 index 0000000000..410cd6dbe8 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalAppLaunchPropertiesAssert.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.assertj + +import com.datadog.android.rum.model.VitalEvent +import org.assertj.core.api.AbstractObjectAssert +import org.assertj.core.api.Assertions.assertThat + +internal class VitalAppLaunchPropertiesAssert(actual: VitalEvent.Vital.AppLaunchProperties) : + AbstractObjectAssert( + actual, + VitalAppLaunchPropertiesAssert::class.java + ) { + + fun hasName(expected: String?) = apply { + assertThat(actual.name).overridingErrorMessage( + "Expected event data to have name $expected but was ${actual.name}" + ).isEqualTo(expected) + } + + fun hasDescription(expected: String?) = apply { + assertThat(actual.description).overridingErrorMessage( + "Expected event data to have description $expected but was ${actual.description}" + ).isEqualTo(expected) + } + + fun hasAppLaunchMetric(expected: VitalEvent.AppLaunchMetric) = apply { + assertThat(actual.appLaunchMetric).overridingErrorMessage( + "Expected event data to have appLaunchMetric $expected but was ${actual.appLaunchMetric}" + ).isEqualTo(expected) + } + + fun hasDuration(expected: Number) = apply { + assertThat(actual.duration).overridingErrorMessage( + "Expected event data to have duration $expected but was ${actual.duration}" + ).isEqualTo(expected) + } + + fun hasStartupType(expected: VitalEvent.StartupType?) = apply { + assertThat(actual.startupType).overridingErrorMessage( + "Expected event data to have startupType $expected but was ${actual.startupType}" + ).isEqualTo(expected) + } + + fun hasPrewarmed(expected: Boolean?) = apply { + assertThat(actual.isPrewarmed).overridingErrorMessage( + "Expected event data to have isPrewarmed $expected but was ${actual.isPrewarmed}" + ).isEqualTo(expected) + } + + fun hasSavedInstanceStateBundle(expected: Boolean?) = apply { + assertThat(actual.hasSavedInstanceStateBundle).overridingErrorMessage( + "Expected event data to have hasSavedInstanceStateBundle " + + "$expected but was ${actual.hasSavedInstanceStateBundle}" + ).isEqualTo(expected) + } + + companion object { + internal fun assertThat( + actual: VitalEvent.Vital.AppLaunchProperties + ): VitalAppLaunchPropertiesAssert = VitalAppLaunchPropertiesAssert(actual) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt index 530943f8e8..ac6ee09510 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt @@ -88,6 +88,14 @@ internal class VitalEventAssert(actual: VitalEvent) : AbstractObjectAssert { hasField("type", vital.type) @@ -575,29 +575,44 @@ internal class RumEventSerializerTest { vital.stepType?.let { hasField("step_type", it.toJson()) } vital.failureReason?.let { hasField("failure_reason", it.toJson()) } } - is VitalEvent.Vital.DurationProperties, - is VitalEvent.Vital.AppLaunchProperties -> TODO("SDK doesn't support this vital type yet") + + is VitalEvent.Vital.AppLaunchProperties -> { + hasField("type", vital.type) + hasField("id", vital.id) + vital.name?.let { hasField("name", it) } + vital.description?.let { hasField("description", it) } + hasField("app_launch_metric", vital.appLaunchMetric.toJson()) + hasField("duration", vital.duration) + vital.startupType?.let { hasField("startup_type", it.toJson()) } + vital.isPrewarmed?.let { hasField("is_prewarmed", it) } + vital.hasSavedInstanceStateBundle?.let { hasField("has_saved_instance_state_bundle", it) } + } + + is VitalEvent.Vital.DurationProperties -> TODO("SDK doesn't support this vital type yet") } } - .hasField("application") { + hasField("application") { hasField("id", event.application.id) } - .hasField("session") { + hasField("session") { hasField("id", event.session.id) hasField("type", event.session.type.name.lowercase(Locale.US)) event.session.hasReplay?.let { hasField("has_replay", it) } } - .hasField("view") { - val view = checkNotNull(event.view) - hasField("id", view.id) - hasField("url", view.url) - view.referrer?.let { hasField("referrer", it) } - view.name?.let { hasField("name", it) } + event.view?.let { + hasField("view") { + val view = checkNotNull(event.view) + hasField("id", view.id) + hasField("url", view.url) + view.referrer?.let { hasField("referrer", it) } + view.name?.let { hasField("name", it) } + } } - .hasField("_dd") { + hasField("_dd") { event.dd.browserSdkVersion?.let { hasField("browser_sdk_version", it) } } - .hasNullableField("service", event.service) + hasNullableField("service", event.service) + } event.device?.let { device -> assertThat(jsonObject).hasField("device") { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt index df709cb7a9..309cadbb66 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeAttributePropagationTest.kt @@ -207,7 +207,15 @@ internal class RumApplicationScopeAttributePropagationTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = fakeSampleRate, + internalLogger = mockInternalLogger + ), + featuresContextResolver = FeaturesContextResolver() ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt index 18b73af121..61e122e4e8 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt @@ -20,6 +20,7 @@ import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.RumActionType import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.InfoProvider import com.datadog.android.rum.internal.domain.accessibility.AccessibilitySnapshotManager import com.datadog.android.rum.internal.domain.battery.BatteryInfo @@ -183,7 +184,15 @@ internal class RumApplicationScopeTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = fakeSampleRate, + internalLogger = mockInternalLogger + ), + featuresContextResolver = FeaturesContextResolver() ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt index 5d8d1be210..13bc848f60 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeAttributePropagationTest.kt @@ -182,7 +182,15 @@ internal class RumSessionScopeAttributePropagationTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = fakeSampleRate, + internalLogger = mockInternalLogger + ), + featuresContextResolver = FeaturesContextResolver() ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt index 9a2e15c220..f653dbf9b1 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScopeTest.kt @@ -13,11 +13,16 @@ import com.datadog.android.api.feature.EventWriteScope import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureScope 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.api.storage.NoOpDataWriter import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType +import com.datadog.android.rum.assertj.VitalAppLaunchPropertiesAssert +import com.datadog.android.rum.assertj.VitalEventAssert +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.InfoProvider import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.accessibility.AccessibilitySnapshotManager @@ -29,10 +34,15 @@ import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter import com.datadog.android.rum.internal.startup.RumStartupScenario import com.datadog.android.rum.internal.startup.RumTTIDInfo import com.datadog.android.rum.internal.startup.testRumStartupScenarios +import com.datadog.android.rum.internal.toVital +import com.datadog.android.rum.internal.utils.buildDDTagsString import com.datadog.android.rum.internal.vitals.VitalMonitor import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier +import com.datadog.android.rum.model.ViewEvent +import com.datadog.android.rum.model.VitalEvent import com.datadog.android.rum.utils.forge.Configurator +import com.datadog.tools.unit.forge.exhaustiveAttributes import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.FloatForgery @@ -54,6 +64,7 @@ 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.doReturn import org.mockito.kotlin.eq import org.mockito.kotlin.inOrder @@ -163,11 +174,27 @@ internal class RumSessionScopeTest { private var fakeRumSessionType: RumSessionType? = null + @Mock + lateinit var mockEventBatchWriter: EventBatchWriter + + lateinit var fakeParentAttributes: Map + + @Forgery + lateinit var fakeBatteryInfo: BatteryInfo + + @Forgery + lateinit var fakeDisplayInfo: DisplayInfo + + @BoolForgery + var fakeHasReplay: Boolean = false + + private var fakeVitalSource: VitalEvent.VitalEventSource? = null + @BeforeEach fun `set up`(forge: Forge) { fakeInitialViewEvent = forge.startViewEvent() - whenever(mockParentScope.getRumContext()) doReturn fakeParentContext + whenever(mockParentScope.getRumContext()).doAnswer { fakeParentContext } whenever(mockChildScope.handleEvent(any(), any(), any(), any())) doReturn mockChildScope whenever(mockSdkCore.getFeature(Feature.SESSION_REPLAY_FEATURE_NAME)) doReturn mockSessionReplayFeatureScope @@ -176,6 +203,38 @@ internal class RumSessionScopeTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } + whenever(mockEventWriteScope.invoke(any())) doAnswer { + val callback = it.getArgument<(EventBatchWriter) -> Unit>(0) + callback.invoke(mockEventBatchWriter) + } + whenever(mockWriter.write(eq(mockEventBatchWriter), any(), eq(EventType.DEFAULT))) doReturn true + + whenever(mockBatteryInfoProvider.getState()) doReturn fakeBatteryInfo + whenever(mockDisplayInfoProvider.getState()) doReturn fakeDisplayInfo + + fakeParentAttributes = forge.exhaustiveAttributes() + whenever(mockParentScope.getCustomAttributes()) doReturn fakeParentAttributes + + val isValidSource = forge.aBool() + + val fakeSource = if (isValidSource) { + forge.anElementFrom( + ViewEvent.ViewEventSource.values().map { it.toJson().asString } + ) + } else { + forge.anAlphabeticalString() + } + + fakeDatadogContext = fakeDatadogContext.copy( + source = fakeSource + ) + + fakeVitalSource = if (isValidSource) { + VitalEvent.VitalEventSource.fromJson(fakeSource) + } else { + null + } + initializeTestedScope() } @@ -1506,6 +1565,99 @@ internal class RumSessionScopeTest { } } + @ParameterizedTest + @MethodSource("testScenarios") + fun `M write Vital event with TTID W handleEvent { AppLaunchTTIDEvent }`( + scenario: RumStartupScenario, + forge: Forge + ) { + // Given + fakeParentContext = fakeParentContext.copy( + syntheticsTestId = null, + syntheticsResultId = null + ) + + val mockView = mock() + val fakeViewId = forge.aString() + whenever(mockView.viewId) doReturn fakeViewId + whenever(mockChildScope.activeView) doReturn mockView + + fakeDatadogContext = fakeDatadogContext.copy( + featuresContext = fakeDatadogContext.featuresContext.toMutableMap().apply { + put(Feature.SESSION_REPLAY_FEATURE_NAME, mapOf(fakeViewId to mapOf("has_replay" to fakeHasReplay))) + } + ) + + val info = RumTTIDInfo( + scenario = scenario, + durationNs = forge.aLong(min = 0, max = 10000) + ) + + val event = RumRawEvent.AppStartTTIDEvent( + info = info + ) + + // When + + val result = testedScope.handleEvent( + event = event, + datadogContext = fakeDatadogContext, + writeScope = mockEventWriteScope, + writer = mockWriter + ) + + // Then + val context = checkNotNull(result).getRumContext() + + argumentCaptor { + verify(mockWriter).write(eq(mockEventBatchWriter), capture(), eq(EventType.DEFAULT)) + VitalEventAssert.assertThat(lastValue).apply { + hasDate(scenario.initialTime.timestamp + fakeTimeInfo.serverTimeOffsetMs) + hasApplicationId(context.applicationId) + containsExactlyContextAttributes(fakeParentAttributes) + hasStartReason(context.sessionStartReason) + hasSampleRate(100f) + hasNoSyntheticsTest() + hasSessionId(context.sessionId) + hasSessionType(fakeRumSessionType?.toVital() ?: VitalEvent.VitalEventSessionType.USER) + hasSessionReplay(fakeHasReplay) + hasNullView() + hasSource(fakeVitalSource) + hasAccountInfo(fakeDatadogContext.accountInfo) + hasUserInfo(fakeDatadogContext.userInfo) + hasDeviceInfo( + fakeDatadogContext.deviceInfo.deviceName, + fakeDatadogContext.deviceInfo.deviceModel, + fakeDatadogContext.deviceInfo.deviceBrand, + fakeDatadogContext.deviceInfo.deviceType.toVitalSchemaType(), + fakeDatadogContext.deviceInfo.architecture + ) + hasOsInfo( + fakeDatadogContext.deviceInfo.osName, + fakeDatadogContext.deviceInfo.osVersion, + fakeDatadogContext.deviceInfo.osMajorVersion + ) + hasConnectivityInfo(fakeDatadogContext.networkInfo) + hasVersion(fakeDatadogContext.version) + hasServiceName(fakeDatadogContext.service) + hasDDTags(buildDDTagsString(fakeDatadogContext)) + } + + val vital = lastValue.vital + check(vital is VitalEvent.Vital.AppLaunchProperties) + + VitalAppLaunchPropertiesAssert.assertThat(vital).apply { + hasName(null) + hasDescription(null) + hasAppLaunchMetric(VitalEvent.AppLaunchMetric.TTID) + hasDuration(info.durationNs) + hasStartupType(scenario.toVitalStartupType()) + hasPrewarmed(null) + hasSavedInstanceStateBundle(scenario.hasSavedInstanceStateBundle) + } + } + } + // endregion // region Internal @@ -1538,7 +1690,15 @@ internal class RumSessionScopeTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = sampleRate, + internalLogger = mock() + ), + featuresContextResolver = FeaturesContextResolver() ) if (withMockChildScope) { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeAttributePropagationTest.kt index 432c1bb4be..4430213900 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeAttributePropagationTest.kt @@ -171,7 +171,14 @@ internal class RumViewManagerScopeAttributePropagationTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumSessionTypeOverride = fakeRumSessionType + rumSessionTypeOverride = fakeRumSessionType, + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = fakeSampleRate, + internalLogger = mockInternalLogger + ) ) } 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 6e94b8319e..6c4524e34f 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 @@ -152,6 +152,7 @@ internal class RumViewManagerScopeTest { private var fakeSampleRate: Float = 0.0f private var fakeRumSessionType: RumSessionType? = null + private lateinit var rumVitalEventHelper: RumVitalEventHelper @BeforeEach fun `set up`(forge: Forge) { @@ -168,6 +169,13 @@ internal class RumViewManagerScopeTest { fakeRumSessionType = forge.aNullable { aValueFrom(RumSessionType::class.java) } + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = fakeSampleRate, + internalLogger = mockInternalLogger + ) testedScope = RumViewManagerScope( mockParentScope, mockSdkCore, @@ -187,7 +195,8 @@ internal class RumViewManagerScopeTest { rumSessionTypeOverride = fakeRumSessionType, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, - displayInfoProvider = mockDisplayInfoProvider + displayInfoProvider = mockDisplayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) } @@ -573,7 +582,8 @@ internal class RumViewManagerScopeTest { rumSessionTypeOverride = fakeRumSessionType, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, - displayInfoProvider = mockDisplayInfoProvider + displayInfoProvider = mockDisplayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) testedScope.applicationDisplayed = true val fakeEvent = forge.validBackgroundEvent() @@ -609,7 +619,8 @@ internal class RumViewManagerScopeTest { rumSessionTypeOverride = fakeRumSessionType, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, - displayInfoProvider = mockDisplayInfoProvider + displayInfoProvider = mockDisplayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) testedScope.childrenScopes.add(mockChildScope) whenever(mockChildScope.isActive()) doReturn true @@ -648,7 +659,8 @@ internal class RumViewManagerScopeTest { rumSessionTypeOverride = fakeRumSessionType, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, - displayInfoProvider = mockDisplayInfoProvider + displayInfoProvider = mockDisplayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) testedScope.applicationDisplayed = true val fakeEvent = forge.validBackgroundEvent() @@ -720,7 +732,8 @@ internal class RumViewManagerScopeTest { rumSessionTypeOverride = fakeRumSessionType, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, - displayInfoProvider = mockDisplayInfoProvider + displayInfoProvider = mockDisplayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) testedScope.childrenScopes.add(mockChildScope) whenever(mockChildScope.isActive()) doReturn true @@ -760,7 +773,8 @@ internal class RumViewManagerScopeTest { rumSessionTypeOverride = fakeRumSessionType, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, - displayInfoProvider = mockDisplayInfoProvider + displayInfoProvider = mockDisplayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) testedScope.stopped = true val fakeEvent = forge.applicationStartedEvent() diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeAttributePropagationTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeAttributePropagationTest.kt index 16e77a2794..6382a15064 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeAttributePropagationTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeAttributePropagationTest.kt @@ -747,7 +747,14 @@ internal class RumViewScopeAttributePropagationTest { accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, - rumSessionTypeOverride = rumSessionType + rumSessionTypeOverride = rumSessionType, + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = rumSessionType, + batteryInfoProvider = batteryInfoProvider, + displayInfoProvider = displayInfoProvider, + sampleRate = sampleRate, + internalLogger = sdkCore.internalLogger + ) ) // endregion 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 dc1bc58f7d..ff66456dc6 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 @@ -9188,7 +9188,14 @@ internal class RumViewScopeTest { rumSessionTypeOverride = fakeRumSessionType, accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, - displayInfoProvider = mockDisplayInfoProvider + displayInfoProvider = mockDisplayInfoProvider, + rumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = sampleRate, + internalLogger = sdkCore.internalLogger + ) ) data class RumRawEventData(val event: RumRawEvent, val viewKey: RumScopeKey) 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 11ac5a5eb2..f87470340b 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 @@ -31,6 +31,7 @@ import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.featureoperations.FailureReason +import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.debug.RumDebugListener @@ -46,6 +47,7 @@ import com.datadog.android.rum.internal.domain.scope.RumScopeKey import com.datadog.android.rum.internal.domain.scope.RumSessionScope import com.datadog.android.rum.internal.domain.scope.RumViewManagerScope import com.datadog.android.rum.internal.domain.scope.RumViewScope +import com.datadog.android.rum.internal.domain.scope.RumVitalEventHelper import com.datadog.android.rum.internal.domain.state.ViewUIPerformanceReport import com.datadog.android.rum.internal.metric.SessionMetricDispatcher import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener @@ -269,7 +271,9 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) testedMonitor.rootScope = mockApplicationScope } @@ -300,7 +304,9 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) // When @@ -373,7 +379,9 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -415,7 +423,9 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -2032,7 +2042,9 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) // When @@ -2071,7 +2083,9 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) // When @@ -2111,7 +2125,9 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionTypeOverride = null, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) whenever(mockExecutorService.isShutdown).thenReturn(true) @@ -2283,7 +2299,9 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper(), + featuresContextResolver = FeaturesContextResolver() ) testedMonitor.startView(key, name, attributes) // When @@ -3047,4 +3065,12 @@ internal class DatadogRumMonitorTest { const val PROCESSING_DELAY = 100L const val DEFAULT_API_USAGE_SAMPLING_RATE = 15f } + + private fun createRumVitalEventHelper(): RumVitalEventHelper = RumVitalEventHelper( + rumSessionTypeOverride = fakeRumSessionType, + batteryInfoProvider = mockBatteryInfoProvider, + displayInfoProvider = mockDisplayInfoProvider, + sampleRate = fakeSampleRate, + internalLogger = mockInternalLogger + ) } diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImplTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImplTest.kt index d28d3ea426..6d70ef3be2 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImplTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumAppStartupDetectorImplTest.kt @@ -13,6 +13,7 @@ import android.app.Application import android.os.Build import android.os.Bundle import com.datadog.android.core.internal.system.BuildSdkVersionProvider +import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.utils.forge.Configurator import com.datadog.tools.unit.extensions.TestConfigurationExtension import fr.xgouchet.elmyr.Forge @@ -105,7 +106,7 @@ internal class RumAppStartupDetectorImplTest { // Then listener.verifyScenarioDetected( RumStartupScenario.Cold( - initialTimeNs = 0, + initialTime = Time(0, 0), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), appStartActivityOnCreateGapNs = 3.seconds.inWholeNanoseconds @@ -137,7 +138,10 @@ internal class RumAppStartupDetectorImplTest { // Then listener.verifyScenarioDetected( RumStartupScenario.WarmFirstActivity( - initialTimeNs = currentTime.inWholeNanoseconds, + initialTime = Time( + nanoTime = currentTime.inWholeNanoseconds, + timestamp = currentTime.inWholeMilliseconds + ), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), appStartActivityOnCreateGapNs = 3.seconds.inWholeNanoseconds @@ -165,7 +169,10 @@ internal class RumAppStartupDetectorImplTest { listener.verifyScenarioDetected( RumStartupScenario.WarmFirstActivity( - initialTimeNs = currentTime.inWholeNanoseconds, + initialTime = Time( + nanoTime = currentTime.inWholeNanoseconds, + timestamp = currentTime.inWholeMilliseconds + ), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), appStartActivityOnCreateGapNs = 6.seconds.inWholeNanoseconds @@ -210,7 +217,7 @@ internal class RumAppStartupDetectorImplTest { inOrder(listener) { listener.verifyScenarioDetected( RumStartupScenario.Cold( - initialTimeNs = 0, + initialTime = Time(0, 0), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), appStartActivityOnCreateGapNs = 30.seconds.inWholeNanoseconds @@ -219,7 +226,10 @@ internal class RumAppStartupDetectorImplTest { listener.verifyScenarioDetected( RumStartupScenario.WarmAfterActivityDestroyed( - initialTimeNs = currentTime.inWholeNanoseconds, + initialTime = Time( + nanoTime = currentTime.inWholeNanoseconds, + timestamp = currentTime.inWholeMilliseconds + ), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle2, activity = activity.wrapWeak() ) @@ -262,7 +272,7 @@ internal class RumAppStartupDetectorImplTest { // Then listener.verifyScenarioDetected( RumStartupScenario.Cold( - initialTimeNs = 0, + initialTime = Time(0, 0), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), appStartActivityOnCreateGapNs = 3.seconds.inWholeNanoseconds @@ -309,7 +319,7 @@ internal class RumAppStartupDetectorImplTest { // Then listener.verifyScenarioDetected( RumStartupScenario.Cold( - initialTimeNs = 0, + initialTime = Time(0, 0), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), appStartActivityOnCreateGapNs = 3.seconds.inWholeNanoseconds @@ -360,7 +370,7 @@ internal class RumAppStartupDetectorImplTest { inOrder(listener) { listener.verifyScenarioDetected( RumStartupScenario.Cold( - initialTimeNs = 0, + initialTime = Time(0, 0), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), appStartActivityOnCreateGapNs = 30.seconds.inWholeNanoseconds @@ -368,7 +378,10 @@ internal class RumAppStartupDetectorImplTest { ) listener.verifyScenarioDetected( RumStartupScenario.WarmAfterActivityDestroyed( - initialTimeNs = currentTime.inWholeNanoseconds, + initialTime = Time( + nanoTime = currentTime.inWholeNanoseconds, + timestamp = currentTime.inWholeMilliseconds + ), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle2, activity = activity2.wrapWeak() ) @@ -439,16 +452,19 @@ internal class RumAppStartupDetectorImplTest { inOrder(listener) { listener.verifyScenarioDetected( RumStartupScenario.Cold( - initialTimeNs = 0, hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = activity.wrapWeak(), - appStartActivityOnCreateGapNs = 30.seconds.inWholeNanoseconds + appStartActivityOnCreateGapNs = 30.seconds.inWholeNanoseconds, + initialTime = Time(0, 0) ) ) listener.verifyScenarioDetected( RumStartupScenario.WarmAfterActivityDestroyed( - initialTimeNs = currentTime.inWholeNanoseconds, + initialTime = Time( + nanoTime = currentTime.inWholeNanoseconds, + timestamp = currentTime.inWholeMilliseconds + ), hasSavedInstanceStateBundle = hasSavedInstanceStateBundle3, activity = activity3.wrapWeak() ) @@ -464,9 +480,14 @@ internal class RumAppStartupDetectorImplTest { val detector = RumAppStartupDetectorImpl( application = application, buildSdkVersionProvider = buildSdkVersionProvider, - appStartupTimeProviderNs = { 0 }, + appStartupTimeProvider = { Time(0, 0) }, processImportanceProvider = { processImportance }, - timeProviderNs = { currentTime.inWholeNanoseconds }, + timeProvider = { + Time( + timestamp = currentTime.inWholeMilliseconds, + nanoTime = currentTime.inWholeNanoseconds + ) + }, listener ) @@ -498,7 +519,7 @@ internal class RumAppStartupDetectorImplTest { argThat { actual -> (actual.activity.get() == expected.activity.get()) && (actual.hasSavedInstanceStateBundle == expected.hasSavedInstanceStateBundle) && - (actual.initialTimeNs == expected.initialTimeNs) && + (actual.initialTime == expected.initialTime) && (actual.javaClass == expected.javaClass) } ) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporterTest.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporterTest.kt index 9de492a3f0..d7b0dc21a4 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporterTest.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumFirstDrawTimeReporterTest.kt @@ -13,6 +13,7 @@ import android.view.View import android.view.ViewTreeObserver import android.view.Window import com.datadog.android.api.InternalLogger +import com.datadog.android.rum.internal.domain.Time import com.datadog.android.rum.internal.utils.window.RumWindowCallbackListener import com.datadog.android.rum.internal.utils.window.RumWindowCallbacksRegistry import com.datadog.android.rum.utils.forge.Configurator @@ -87,7 +88,7 @@ class RumFirstDrawTimeReporterTest { weakActivity = WeakReference(activity) scenario = RumStartupScenario.Cold( - initialTimeNs = 0, + initialTime = Time(0, 0), hasSavedInstanceStateBundle = true, activity = weakActivity, appStartActivityOnCreateGapNs = 0.seconds.inWholeNanoseconds diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumTTIDInfoUtils.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumTTIDInfoUtils.kt index e41612d7f6..1760e830fa 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumTTIDInfoUtils.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/startup/RumTTIDInfoUtils.kt @@ -7,28 +7,30 @@ package com.datadog.android.rum.internal.startup import android.app.Activity +import com.datadog.android.rum.internal.domain.Time import fr.xgouchet.elmyr.Forge import java.lang.ref.WeakReference internal fun Forge.testRumStartupScenarios(weakActivity: WeakReference): List { val initialTimeNanos = aLong(min = 0, max = 1000000) + val initialTime = Time(timestamp = initialTimeNanos / 1000000, nanoTime = initialTimeNanos) val hasSavedInstanceStateBundle = aBool() return listOf( RumStartupScenario.Cold( - initialTimeNs = initialTimeNanos, + initialTime = initialTime, hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = weakActivity, appStartActivityOnCreateGapNs = aLong(min = 0, max = 10000) ), RumStartupScenario.WarmFirstActivity( - initialTimeNs = initialTimeNanos, + initialTime = initialTime, hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = weakActivity, appStartActivityOnCreateGapNs = aLong(min = 0, max = 10000) ), RumStartupScenario.WarmAfterActivityDestroyed( - initialTimeNs = initialTimeNanos, + initialTime = initialTime, hasSavedInstanceStateBundle = hasSavedInstanceStateBundle, activity = weakActivity ) diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/BatteryInfoForgeryFactory.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/BatteryInfoForgeryFactory.kt new file mode 100644 index 0000000000..fa9d88f146 --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/BatteryInfoForgeryFactory.kt @@ -0,0 +1,20 @@ +/* + * 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.tests.elmyr + +import com.datadog.android.rum.internal.domain.battery.BatteryInfo +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class BatteryInfoForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): BatteryInfo { + return BatteryInfo( + batteryLevel = forge.aNullable { aFloat(min = 0.0f, max = 1.0f) }, + lowPowerMode = forge.aNullable { aBool() } + ) + } +} diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/DisplayInfoForgeryFactory.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/DisplayInfoForgeryFactory.kt new file mode 100644 index 0000000000..27ef80c7dd --- /dev/null +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/tests/elmyr/DisplayInfoForgeryFactory.kt @@ -0,0 +1,19 @@ +/* + * 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.tests.elmyr + +import com.datadog.android.rum.internal.domain.display.DisplayInfo +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.ForgeryFactory + +internal class DisplayInfoForgeryFactory : ForgeryFactory { + override fun getForgery(forge: Forge): DisplayInfo { + return DisplayInfo( + screenBrightness = forge.aNullable { aFloat() } + ) + } +} 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 4fa0d4c474..e5d0c32dd8 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 @@ -13,6 +13,8 @@ import com.datadog.android.internal.tests.elmyr.InternalTelemetryErrorLogForgery import com.datadog.android.internal.tests.elmyr.InternalTelemetryEventForgeryFactory import com.datadog.android.internal.tests.elmyr.InternalTelemetryMetricForgeryFactory import com.datadog.android.internal.tests.elmyr.TracingHeaderTypesSetForgeryFactory +import com.datadog.android.rum.tests.elmyr.BatteryInfoForgeryFactory +import com.datadog.android.rum.tests.elmyr.DisplayInfoForgeryFactory import com.datadog.android.rum.tests.elmyr.ResourceIdForgeryFactory import com.datadog.android.rum.tests.elmyr.RumScopeKeyForgeryFactory import com.datadog.android.tests.elmyr.useCoreFactories @@ -50,6 +52,8 @@ internal class Configurator : BaseConfigurator() { forge.addFactory(FrameMetricDataForgeryFactory()) forge.addFactory(ViewUIPerformanceReportForgeryFactory()) forge.addFactory(SlowFramesConfigurationForgeryFactory()) + forge.addFactory(DisplayInfoForgeryFactory()) + forge.addFactory(BatteryInfoForgeryFactory()) // Telemetry schema models forge.addFactory(TelemetryDebugEventForgeryFactory()) diff --git a/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/utils/forge/VitalEventForgeryFactory.kt b/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/utils/forge/VitalEventForgeryFactory.kt index 72c8844b75..66b3dbc5bf 100644 --- a/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/utils/forge/VitalEventForgeryFactory.kt +++ b/features/dd-sdk-android-rum/src/testFixtures/kotlin/com/datadog/android/rum/utils/forge/VitalEventForgeryFactory.kt @@ -19,6 +19,8 @@ import java.util.UUID class VitalEventForgeryFactory : ForgeryFactory { override fun getForgery(forge: Forge): VitalEvent { + val typeIndex = forge.anInt(min = 0, max = 2) + return VitalEvent( date = forge.aTimestamp(), application = VitalEvent.Application(forge.getForgery().toString()), @@ -58,19 +60,33 @@ class VitalEventForgeryFactory : ForgeryFactory { browserSdkVersion = forge.aNullable { aStringMatching("\\d+\\.\\d+\\.\\d+") } ), ddtags = forge.aNullable { ddTagsString() }, - view = VitalEvent.VitalEventView( - id = forge.getForgery().toString(), - referrer = forge.aNullable { getForgery().toString() }, - url = forge.aStringMatching("https://[a-z]+.[a-z]{3}/[a-z0-9_/]+"), - name = forge.aNullable { anAlphabeticalString() } - ), - vital = VitalEvent.Vital.FeatureOperationProperties( - id = forge.aString(), - operationKey = forge.aNullable { aString() }, - name = forge.anAlphabeticalString(), - stepType = forge.aValueFrom(StepType::class.java), - failureReason = forge.aNullable { aValueFrom(FailureReason::class.java) } - ) + view = forge.aNullable { + VitalEvent.VitalEventView( + id = forge.getForgery().toString(), + referrer = forge.aNullable { getForgery().toString() }, + url = forge.aStringMatching("https://[a-z]+.[a-z]{3}/[a-z0-9_/]+"), + name = forge.aNullable { anAlphabeticalString() } + ) + }, + vital = when (typeIndex) { + 0 -> VitalEvent.Vital.FeatureOperationProperties( + id = forge.aString(), + operationKey = forge.aNullable { aString() }, + name = forge.anAlphabeticalString(), + stepType = forge.aValueFrom(StepType::class.java), + failureReason = forge.aNullable { aValueFrom(FailureReason::class.java) } + ) + else -> VitalEvent.Vital.AppLaunchProperties( + id = forge.aString(), + name = forge.aNullable { forge.aString() }, + description = forge.aNullable { forge.aString() }, + appLaunchMetric = forge.aValueFrom(VitalEvent.AppLaunchMetric::class.java), + duration = forge.aLong(min = 0), + startupType = forge.aValueFrom(VitalEvent.StartupType::class.java), + isPrewarmed = forge.aNullable { aBool() }, + hasSavedInstanceStateBundle = forge.aNullable { aBool() } + ) + } ) } } From fcd356bd78a418f159fad06330d9dba2fb9e2885 Mon Sep 17 00:00:00 2001 From: Aleksandr Gringauz Date: Thu, 16 Oct 2025 12:06:21 +0200 Subject: [PATCH 2/2] RUM-11784: PR fixes --- .../kotlin/com/datadog/android/rum/Rum.kt | 4 +-- .../domain/scope/RumApplicationScope.kt | 10 +++----- .../internal/domain/scope/RumSessionScope.kt | 10 ++------ .../rum/internal/monitor/DatadogRumMonitor.kt | 7 ++---- .../android/rum/assertj/VitalEventAssert.kt | 8 ++++++ ...pplicationScopeAttributePropagationTest.kt | 3 +-- .../domain/scope/RumApplicationScopeTest.kt | 4 +-- ...RumSessionScopeAttributePropagationTest.kt | 3 +-- .../domain/scope/RumSessionScopeTest.kt | 25 +++---------------- .../internal/monitor/DatadogRumMonitorTest.kt | 25 ++++++------------- 10 files changed, 31 insertions(+), 68 deletions(-) diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt index edb69cb5d7..0d7211436a 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/Rum.kt @@ -16,7 +16,6 @@ import com.datadog.android.api.feature.Feature import com.datadog.android.api.feature.FeatureSdkCore import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.RateBasedSampler -import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumAnonymousIdentifierManager import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.domain.scope.RumVitalEventHelper @@ -154,8 +153,7 @@ object Rum { displayInfoProvider = rumFeature.displayInfoProvider, sampleRate = rumFeature.sampleRate, internalLogger = sdkCore.internalLogger - ), - featuresContextResolver = FeaturesContextResolver() + ) ) } diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt index 33f1d9f4a9..4c0b7906f9 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt @@ -18,7 +18,6 @@ import com.datadog.android.rum.DdRumContentProvider import com.datadog.android.rum.GlobalRumMonitor import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.InfoProvider import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time @@ -54,8 +53,7 @@ internal class RumApplicationScope( private val batteryInfoProvider: InfoProvider, private val displayInfoProvider: InfoProvider, private val rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter, - private val rumVitalEventHelper: RumVitalEventHelper, - private val featuresContextResolver: FeaturesContextResolver + private val rumVitalEventHelper: RumVitalEventHelper ) : RumScope, RumViewChangedListener { override val parentScope: RumScope? = null @@ -85,8 +83,7 @@ internal class RumApplicationScope( batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, - rumVitalEventHelper = rumVitalEventHelper, - featuresContextResolver = featuresContextResolver + rumVitalEventHelper = rumVitalEventHelper ) ) @@ -208,8 +205,7 @@ internal class RumApplicationScope( batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, - rumVitalEventHelper = rumVitalEventHelper, - featuresContextResolver = featuresContextResolver + rumVitalEventHelper = rumVitalEventHelper ) childScopes.add(newSession) if (event !is RumRawEvent.StartView) { diff --git a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt index 3722d80673..5b565c804f 100644 --- a/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt +++ b/features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt @@ -16,7 +16,6 @@ import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType -import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.domain.InfoProvider import com.datadog.android.rum.internal.domain.RumContext import com.datadog.android.rum.internal.domain.Time @@ -62,8 +61,7 @@ internal class RumSessionScope( private val sessionMaxDurationNanos: Long = DEFAULT_SESSION_MAX_DURATION_NS, rumSessionTypeOverride: RumSessionType?, private val rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter, - private val rumVitalEventHelper: RumVitalEventHelper, - private val featuresContextResolver: FeaturesContextResolver + private val rumVitalEventHelper: RumVitalEventHelper ) : RumScope { internal var sessionId = RumContext.NULL_UUID @@ -315,17 +313,13 @@ internal class RumSessionScope( ): VitalEvent { val rumContext = getRumContext() - val hasReplay = childScope?.activeView?.viewId?.let { - featuresContextResolver.resolveViewHasReplay(datadogContext, it) - } - return rumVitalEventHelper.newVitalEvent( timestampMs = event.info.scenario.initialTime.timestamp + sdkCore.time.serverTimeOffsetMs, datadogContext = datadogContext, eventAttributes = emptyMap(), customAttributes = getCustomAttributes(), view = null, - hasReplay = hasReplay, + hasReplay = null, rumContext = rumContext, vital = VitalEvent.Vital.AppLaunchProperties( id = UUID.randomUUID().toString(), 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 edeb9f45f5..8055cba771 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 @@ -40,7 +40,6 @@ import com.datadog.android.rum.RumSessionType import com.datadog.android.rum._RumInternalProxy import com.datadog.android.rum.featureoperations.FailureReason import com.datadog.android.rum.internal.CombinedRumSessionListener -import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.debug.RumDebugListener @@ -99,8 +98,7 @@ internal class DatadogRumMonitor( batteryInfoProvider: InfoProvider, displayInfoProvider: InfoProvider, rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter, - rumVitalEventHelper: RumVitalEventHelper, - private val featuresContextResolver: FeaturesContextResolver + rumVitalEventHelper: RumVitalEventHelper ) : RumMonitor, AdvancedRumMonitor { internal var rootScope = RumApplicationScope( @@ -123,8 +121,7 @@ internal class DatadogRumMonitor( batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, - rumVitalEventHelper = rumVitalEventHelper, - featuresContextResolver = featuresContextResolver + rumVitalEventHelper = rumVitalEventHelper ) internal val keepAliveRunnable = Runnable { diff --git a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt index ac6ee09510..46bf462bda 100644 --- a/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt +++ b/features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/assertj/VitalEventAssert.kt @@ -80,6 +80,14 @@ internal class VitalEventAssert(actual: VitalEvent) : AbstractObjectAssert() - val fakeViewId = forge.aString() - whenever(mockView.viewId) doReturn fakeViewId - whenever(mockChildScope.activeView) doReturn mockView - - fakeDatadogContext = fakeDatadogContext.copy( - featuresContext = fakeDatadogContext.featuresContext.toMutableMap().apply { - put(Feature.SESSION_REPLAY_FEATURE_NAME, mapOf(fakeViewId to mapOf("has_replay" to fakeHasReplay))) - } - ) - val info = RumTTIDInfo( scenario = scenario, durationNs = forge.aLong(min = 0, max = 10000) @@ -1598,7 +1583,6 @@ internal class RumSessionScopeTest { ) // When - val result = testedScope.handleEvent( event = event, datadogContext = fakeDatadogContext, @@ -1620,7 +1604,7 @@ internal class RumSessionScopeTest { hasNoSyntheticsTest() hasSessionId(context.sessionId) hasSessionType(fakeRumSessionType?.toVital() ?: VitalEvent.VitalEventSessionType.USER) - hasSessionReplay(fakeHasReplay) + hasNoSessionReplay() hasNullView() hasSource(fakeVitalSource) hasAccountInfo(fakeDatadogContext.accountInfo) @@ -1646,7 +1630,7 @@ internal class RumSessionScopeTest { val vital = lastValue.vital check(vital is VitalEvent.Vital.AppLaunchProperties) - VitalAppLaunchPropertiesAssert.assertThat(vital).apply { + assertThat(vital).apply { hasName(null) hasDescription(null) hasAppLaunchMetric(VitalEvent.AppLaunchMetric.TTID) @@ -1697,8 +1681,7 @@ internal class RumSessionScopeTest { displayInfoProvider = mockDisplayInfoProvider, sampleRate = sampleRate, internalLogger = mock() - ), - featuresContextResolver = FeaturesContextResolver() + ) ) if (withMockChildScope) { 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 f87470340b..19d171cdca 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 @@ -31,7 +31,6 @@ import com.datadog.android.rum.RumResourceMethod import com.datadog.android.rum.RumSessionListener import com.datadog.android.rum.RumSessionType import com.datadog.android.rum.featureoperations.FailureReason -import com.datadog.android.rum.internal.FeaturesContextResolver import com.datadog.android.rum.internal.RumErrorSourceType import com.datadog.android.rum.internal.RumFeature import com.datadog.android.rum.internal.debug.RumDebugListener @@ -272,8 +271,7 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.rootScope = mockApplicationScope } @@ -305,8 +303,7 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) // When @@ -380,8 +377,7 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -424,8 +420,7 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -2043,8 +2038,7 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) // When @@ -2084,8 +2078,7 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) // When @@ -2126,8 +2119,7 @@ internal class DatadogRumMonitorTest { displayInfoProvider = mockDisplayInfoProvider, rumSessionTypeOverride = null, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) whenever(mockExecutorService.isShutdown).thenReturn(true) @@ -2300,8 +2292,7 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, - rumVitalEventHelper = createRumVitalEventHelper(), - featuresContextResolver = FeaturesContextResolver() + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.startView(key, name, attributes) // When