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..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 @@ -18,6 +18,7 @@ import com.datadog.android.core.InternalSdkCore import com.datadog.android.core.sampling.RateBasedSampler 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 +146,14 @@ 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 + ) ) } 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..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 @@ -52,7 +52,8 @@ 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 ) : RumScope, RumViewChangedListener { override val parentScope: RumScope? = null @@ -81,7 +82,8 @@ internal class RumApplicationScope( accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, - rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, + rumVitalEventHelper = rumVitalEventHelper ) ) @@ -202,7 +204,8 @@ internal class RumApplicationScope( accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, - rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, + 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/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..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 @@ -25,10 +25,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 +60,8 @@ 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 ) : RumScope { internal var sessionId = RumContext.NULL_UUID @@ -95,7 +98,8 @@ internal class RumSessionScope( rumSessionTypeOverride = rumSessionTypeOverride, accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, - displayInfoProvider = displayInfoProvider + displayInfoProvider = displayInfoProvider, + rumVitalEventHelper = rumVitalEventHelper ) internal val activeView: RumViewScope? @@ -156,11 +160,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 +286,54 @@ 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() + + return rumVitalEventHelper.newVitalEvent( + timestampMs = event.info.scenario.initialTime.timestamp + sdkCore.time.serverTimeOffsetMs, + datadogContext = datadogContext, + eventAttributes = emptyMap(), + customAttributes = getCustomAttributes(), + view = null, + hasReplay = null, + 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..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 @@ -55,6 +55,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 +97,8 @@ internal class DatadogRumMonitor( accessibilitySnapshotManager: AccessibilitySnapshotManager, batteryInfoProvider: InfoProvider, displayInfoProvider: InfoProvider, - rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter, + rumVitalEventHelper: RumVitalEventHelper ) : RumMonitor, AdvancedRumMonitor { internal var rootScope = RumApplicationScope( @@ -118,7 +120,8 @@ internal class DatadogRumMonitor( accessibilitySnapshotManager = accessibilitySnapshotManager, batteryInfoProvider = batteryInfoProvider, displayInfoProvider = displayInfoProvider, - rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter, + rumVitalEventHelper = rumVitalEventHelper ) 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..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 { 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..dd2d6669e5 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,14 @@ 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 + ) ) } 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..e287650ce8 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 @@ -183,7 +183,14 @@ 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 + ) ) } 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..b290de3c71 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,14 @@ 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 + ) ) } 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..87e510b02f 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,15 @@ 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.Companion.assertThat +import com.datadog.android.rum.assertj.VitalEventAssert 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 +33,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 +63,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 +173,24 @@ 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 + + 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 +199,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 +1561,87 @@ 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 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) + hasNoSessionReplay() + 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) + + 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 +1674,14 @@ 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() + ) ) 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..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 @@ -46,6 +46,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 +270,8 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.rootScope = mockApplicationScope } @@ -300,7 +302,8 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) // When @@ -373,7 +376,8 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -415,7 +419,8 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.start() val mockCallback = mock<(String?) -> Unit>() @@ -2032,7 +2037,8 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) // When @@ -2071,7 +2077,8 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) // When @@ -2111,7 +2118,8 @@ internal class DatadogRumMonitorTest { batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, rumSessionTypeOverride = null, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) whenever(mockExecutorService.isShutdown).thenReturn(true) @@ -2283,7 +2291,8 @@ internal class DatadogRumMonitorTest { accessibilitySnapshotManager = mockAccessibilitySnapshotManager, batteryInfoProvider = mockBatteryInfoProvider, displayInfoProvider = mockDisplayInfoProvider, - rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter + rumAppStartupTelemetryReporter = mockRumAppStartupTelemetryReporter, + rumVitalEventHelper = createRumVitalEventHelper() ) testedMonitor.startView(key, name, attributes) // When @@ -3047,4 +3056,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() } + ) + } ) } }