Skip to content
Open
1 change: 1 addition & 0 deletions detekt_custom_safe_calls.yml
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ datadog:
- "kotlin.collections.listOf(com.datadog.android.rum.model.ErrorEvent.Interface)"
- "kotlin.collections.listOf(com.datadog.android.rum.model.LongTaskEvent.Interface)"
- "kotlin.collections.listOf(com.datadog.android.rum.model.ResourceEvent.Interface)"
- "kotlin.collections.listOf(com.datadog.android.rum.model.RumVitalAppLaunchEvent.Interface)"
- "kotlin.collections.listOf(com.datadog.android.rum.model.RumVitalOperationStepEvent.Interface)"
- "kotlin.collections.listOf(com.datadog.android.rum.model.ViewEvent.Interface)"
- "kotlin.collections.listOf(com.datadog.android.sessionreplay.MapperTypeWrapper)"
Expand Down
2 changes: 2 additions & 0 deletions features/dd-sdk-android-rum/api/apiSurface
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ data class com.datadog.android.rum.RumConfiguration
fun setLongTaskEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.LongTaskEvent>): Builder
DEPRECATED fun setVitalEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.RumVitalOperationStepEvent>): Builder
fun setVitalOperationStepEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.RumVitalOperationStepEvent>): Builder
fun setVitalAppLaunchEventMapper(com.datadog.android.event.EventMapper<com.datadog.android.rum.model.RumVitalAppLaunchEvent>): Builder
Comment on lines 78 to +79
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we want to expose them in such shape. Maybe we can group them and introduce a single method here withVitalEventMappers(operationsMapper = ..., appLaunchMapper = ..., ....).

With current layout these methods seems to be very verbose among the other methods in this Builder.

Maybe we need to ask iOS SDK what will be their approach regarding this as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe we should revert to setVitaEventMapper and use them for the all the vital events 🙂

Copy link
Contributor Author

@aleksandr-gringauz aleksandr-gringauz Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satween What arguments should setVitaEventMapper have? Same as @0xnm suggested?

I was thinking about something like this: https://pl.kotl.in/nNeN0bmfx

Note that currently VitalEvent from this example doesn't exist in our code. OperationStepVital and AppLaunchVital don't have a common parent type. We could change it though.

But I still lean towards the solution with a separate method per vital type.

We started discussing it with @simaoseica-dd, but not finished yet.

fun trackBackgroundEvents(Boolean): Builder
fun trackFrustrations(Boolean): Builder
fun setVitalsUpdateFrequency(com.datadog.android.rum.configuration.VitalsUpdateFrequency): Builder
Expand Down Expand Up @@ -122,6 +123,7 @@ interface com.datadog.android.rum.RumMonitor
fun startFeatureOperation(String, String? = null, Map<String, Any?> = emptyMap())
fun succeedFeatureOperation(String, String? = null, Map<String, Any?> = emptyMap())
fun failFeatureOperation(String, String? = null, com.datadog.android.rum.featureoperations.FailureReason, Map<String, Any?> = emptyMap())
fun reportAppFullyDisplayed()
var debug: Boolean
fun _getInternal(): _RumInternalProxy?
enum com.datadog.android.rum.RumPerformanceMetric
Expand Down
2 changes: 2 additions & 0 deletions features/dd-sdk-android-rum/api/dd-sdk-android-rum.api
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public final class com/datadog/android/rum/RumConfiguration$Builder {
public final fun setSlowFramesConfiguration (Lcom/datadog/android/rum/configuration/SlowFramesConfiguration;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setTelemetrySampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setViewEventMapper (Lcom/datadog/android/rum/event/ViewEventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setVitalAppLaunchEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setVitalEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setVitalOperationStepEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
public final fun setVitalsUpdateFrequency (Lcom/datadog/android/rum/configuration/VitalsUpdateFrequency;)Lcom/datadog/android/rum/RumConfiguration$Builder;
Expand Down Expand Up @@ -156,6 +157,7 @@ public abstract interface class com/datadog/android/rum/RumMonitor {
public abstract fun getDebug ()Z
public abstract fun removeAttribute (Ljava/lang/String;)V
public abstract fun removeViewAttributes (Ljava/util/Collection;)V
public abstract fun reportAppFullyDisplayed ()V
public abstract fun setDebug (Z)V
public abstract fun startAction (Lcom/datadog/android/rum/RumActionType;Ljava/lang/String;Ljava/util/Map;)V
public abstract fun startFeatureOperation (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ 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.RumVitalAppLaunchEventHelper
import com.datadog.android.rum.internal.metric.SessionEndedMetricDispatcher
import com.datadog.android.rum.internal.monitor.DatadogRumMonitor
import com.datadog.android.rum.internal.startup.RumAppStartupTelemetryReporter
import com.datadog.android.rum.internal.startup.RumSessionScopeStartupManager
import com.datadog.android.telemetry.internal.TelemetryEventHandler

/**
Expand Down Expand Up @@ -115,6 +117,16 @@ object Rum {
sessionSamplingRate = rumFeature.configuration.sampleRate
)

val rumVitalAppLaunchEventHelper = RumVitalAppLaunchEventHelper(
rumSessionTypeOverride = rumFeature.configuration.rumSessionTypeOverride,
batteryInfoProvider = rumFeature.batteryInfoProvider,
displayInfoProvider = rumFeature.displayInfoProvider,
sampleRate = rumFeature.sampleRate,
internalLogger = sdkCore.internalLogger
)

val rumAppStartupTelemetryReporter = RumAppStartupTelemetryReporter.create(sdkCore = sdkCore)

return DatadogRumMonitor(
applicationId = rumFeature.applicationId,
sdkCore = sdkCore,
Expand Down Expand Up @@ -145,7 +157,13 @@ object Rum {
accessibilitySnapshotManager = rumFeature.accessibilitySnapshotManager,
batteryInfoProvider = rumFeature.batteryInfoProvider,
displayInfoProvider = rumFeature.displayInfoProvider,
rumAppStartupTelemetryReporter = RumAppStartupTelemetryReporter.create(sdkCore)
rumSessionScopeStartupManagerFactory = {
RumSessionScopeStartupManager.create(
rumVitalAppLaunchEventHelper = rumVitalAppLaunchEventHelper,
sdkCore = sdkCore,
rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter
)
}
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 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.RumVitalAppLaunchEvent
import com.datadog.android.rum.model.RumVitalOperationStepEvent
import com.datadog.android.rum.model.ViewEvent
import com.datadog.android.rum.tracking.ActionTrackingStrategy
Expand Down Expand Up @@ -243,6 +244,18 @@ data class RumConfiguration internal constructor(
return this
}

/**
* Sets the [EventMapper] for the RUM [RumVitalAppLaunchEvent]. You can use this interface implementation
* to modify the [RumVitalAppLaunchEvent] attributes before serialisation.
*
* @param eventMapper the [EventMapper] implementation.
*/
@ExperimentalRumApi
fun setVitalAppLaunchEventMapper(eventMapper: EventMapper<RumVitalAppLaunchEvent>): Builder {
rumConfig = rumConfig.copy(vitalAppLaunchEventMapper = eventMapper)
return this
}

/**
* Enables/Disables tracking RUM event when no Activity is in foreground.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,14 @@ interface RumMonitor {
attributes: Map<String, Any?> = emptyMap()
)

/**
* This method can be used to mark the moment in time when the UI of the app is considered fully displayed.
* The duration between the application launch and this moment of time will be shown as TTFD (time to full display)
* in the RUM session explorer. Only the first call to this method will have any effect for a given RUM session.
*/
@ExperimentalRumApi
fun reportAppFullyDisplayed()

/**
* Utility setting to inspect the active RUM View.
* If set, a debugging outline will be displayed on top of the application, describing the name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ 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.RumVitalAppLaunchEvent
import com.datadog.android.rum.model.RumVitalOperationStepEvent
import com.datadog.android.rum.model.ViewEvent
import com.datadog.android.rum.tracking.ActionTrackingStrategy
Expand Down Expand Up @@ -375,6 +376,7 @@ internal class RumFeature(
actionEventMapper = configuration.actionEventMapper,
longTaskEventMapper = configuration.longTaskEventMapper,
vitalOperationStepEventMapper = configuration.vitalOperationStepEventMapper,
vitalAppLaunchEventMapper = configuration.vitalAppLaunchEventMapper,
telemetryConfigurationMapper = configuration.telemetryConfigurationMapper,
internalLogger = sdkCore.internalLogger
),
Expand Down Expand Up @@ -682,15 +684,17 @@ internal class RumFeature(

override fun onAppStartupDetected(scenario: RumStartupScenario) {
val activity = scenario.activity.get() ?: return
val rumMonitor = (GlobalRumMonitor.get(sdkCore) as? AdvancedRumMonitor) ?: return

rumMonitor.sendAppStartEvent(scenario)

val callback = object : RumFirstDrawTimeReporter.Callback {
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)
rumMonitor.sendTTIDEvent(info)
}
}

Expand Down Expand Up @@ -721,6 +725,7 @@ internal class RumFeature(
val actionEventMapper: EventMapper<ActionEvent>,
val longTaskEventMapper: EventMapper<LongTaskEvent>,
val vitalOperationStepEventMapper: EventMapper<RumVitalOperationStepEvent>,
val vitalAppLaunchEventMapper: EventMapper<RumVitalAppLaunchEvent>,
val telemetryConfigurationMapper: EventMapper<TelemetryConfigurationEvent>,
val backgroundEventTracking: Boolean,
val trackFrustrations: Boolean,
Expand Down Expand Up @@ -773,6 +778,7 @@ internal class RumFeature(
actionEventMapper = NoOpEventMapper(),
longTaskEventMapper = NoOpEventMapper(),
vitalOperationStepEventMapper = NoOpEventMapper(),
vitalAppLaunchEventMapper = NoOpEventMapper(),
telemetryConfigurationMapper = NoOpEventMapper(),
backgroundEventTracking = false,
trackFrustrations = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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.RumVitalAppLaunchEvent
import com.datadog.android.rum.model.RumVitalOperationStepEvent
import com.datadog.android.rum.model.ViewEvent

Expand Down Expand Up @@ -54,3 +55,10 @@ internal fun RumSessionType.toVital(): RumVitalOperationStepEvent.RumVitalOperat
RumSessionType.USER -> RumVitalOperationStepEvent.RumVitalOperationStepEventSessionType.USER
}
}

internal fun RumSessionType.toVitalAppLaunch(): RumVitalAppLaunchEvent.RumVitalAppLaunchEventSessionType {
return when (this) {
RumSessionType.SYNTHETICS -> RumVitalAppLaunchEvent.RumVitalAppLaunchEventSessionType.SYNTHETICS
RumSessionType.USER -> RumVitalAppLaunchEvent.RumVitalAppLaunchEventSessionType.USER
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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.RumVitalAppLaunchEvent
import com.datadog.android.rum.model.RumVitalOperationStepEvent
import com.datadog.android.rum.model.ViewEvent
import com.datadog.android.telemetry.model.TelemetryConfigurationEvent
Expand All @@ -28,6 +29,7 @@ internal data class RumEventMapper(
val actionEventMapper: EventMapper<ActionEvent> = NoOpEventMapper(),
val longTaskEventMapper: EventMapper<LongTaskEvent> = NoOpEventMapper(),
val vitalOperationStepEventMapper: EventMapper<RumVitalOperationStepEvent> = NoOpEventMapper(),
val vitalAppLaunchEventMapper: EventMapper<RumVitalAppLaunchEvent> = NoOpEventMapper(),
val telemetryConfigurationMapper: EventMapper<TelemetryConfigurationEvent> = NoOpEventMapper(),
private val internalLogger: InternalLogger
) : EventMapper<Any> {
Expand Down Expand Up @@ -63,6 +65,7 @@ internal data class RumEventMapper(
is ResourceEvent -> resourceEventMapper.map(event)
is LongTaskEvent -> longTaskEventMapper.map(event)
is RumVitalOperationStepEvent -> vitalOperationStepEventMapper.map(event)
is RumVitalAppLaunchEvent -> vitalAppLaunchEventMapper.map(event)
is TelemetryConfigurationEvent -> telemetryConfigurationMapper.map(event)
is TelemetryDebugEvent,
is TelemetryUsageEvent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ 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.RumVitalAppLaunchEvent
import com.datadog.android.rum.model.RumVitalOperationStepEvent
import com.datadog.android.rum.model.ViewEvent
import com.datadog.android.telemetry.model.TelemetryConfigurationEvent
Expand All @@ -24,6 +25,7 @@ import com.datadog.android.telemetry.model.TelemetryErrorEvent
import com.datadog.android.telemetry.model.TelemetryUsageEvent
import com.google.gson.JsonObject

@Suppress("TooManyFunctions")
internal class RumEventSerializer(
private val internalLogger: InternalLogger,
private val dataConstraints: DataConstraints = DatadogDataConstraints(internalLogger)
Expand All @@ -49,7 +51,10 @@ internal class RumEventSerializer(
serializeLongTaskEvent(model)
}
is RumVitalOperationStepEvent -> {
serializeVitalEvent(model)
serializeVitalOperationStepEvent(model)
}
is RumVitalAppLaunchEvent -> {
serializeVitalAppLaunchEvent(model)
}
is TelemetryDebugEvent -> {
model.toJson().toString()
Expand Down Expand Up @@ -205,7 +210,31 @@ internal class RumEventSerializer(
return extractKnownAttributes(sanitizedModel.toJson().asJsonObject).toString()
}

private fun serializeVitalEvent(model: RumVitalOperationStepEvent): String {
private fun serializeVitalOperationStepEvent(model: RumVitalOperationStepEvent): String {
val sanitizedUser = model.usr?.copy(
additionalProperties = validateUserAttributes(model.usr.additionalProperties)
.safeMapValuesToJson(internalLogger)
.toMutableMap()
)
val sanitizedAccount = model.account?.copy(
additionalProperties = validateAccountAttributes(model.account.additionalProperties)
.safeMapValuesToJson(internalLogger)
.toMutableMap()
)
val sanitizedContext = model.context?.copy(
additionalProperties = validateContextAttributes(model.context.additionalProperties)
.safeMapValuesToJson(internalLogger)
.toMutableMap()
)
val sanitizedModel = model.copy(
usr = sanitizedUser,
account = sanitizedAccount,
context = sanitizedContext
)
return extractKnownAttributes(sanitizedModel.toJson().asJsonObject).toString()
}

private fun serializeVitalAppLaunchEvent(model: RumVitalAppLaunchEvent): String {
val sanitizedUser = model.usr?.copy(
additionalProperties = validateUserAttributes(model.usr.additionalProperties)
.safeMapValuesToJson(internalLogger)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import com.datadog.android.rum.internal.domain.battery.BatteryInfo
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.startup.RumSessionScopeStartupManager
import com.datadog.android.rum.internal.vitals.VitalMonitor
import com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier
import com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier
Expand All @@ -52,7 +52,7 @@ internal class RumApplicationScope(
private val accessibilitySnapshotManager: AccessibilitySnapshotManager,
private val batteryInfoProvider: InfoProvider<BatteryInfo>,
private val displayInfoProvider: InfoProvider<DisplayInfo>,
private val rumAppStartupTelemetryReporter: RumAppStartupTelemetryReporter
private val rumSessionScopeStartupManagerFactory: () -> RumSessionScopeStartupManager
) : RumScope, RumViewChangedListener {

override val parentScope: RumScope? = null
Expand Down Expand Up @@ -81,7 +81,7 @@ internal class RumApplicationScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter
rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory
)
)

Expand Down Expand Up @@ -202,7 +202,7 @@ internal class RumApplicationScope(
accessibilitySnapshotManager = accessibilitySnapshotManager,
batteryInfoProvider = batteryInfoProvider,
displayInfoProvider = displayInfoProvider,
rumAppStartupTelemetryReporter = rumAppStartupTelemetryReporter
rumSessionScopeStartupManagerFactory = rumSessionScopeStartupManagerFactory
)
childScopes.add(newSession)
if (event !is RumRawEvent.StartView) {
Expand Down
Loading
Loading