Skip to content

Commit 1e7d5f4

Browse files
authored
Merge pull request #2541 from DataDog/tvaleev/RUM-8947/configuration_support
[RUM-8947] - configuration support
2 parents 7e4029e + 213d6c2 commit 1e7d5f4

File tree

15 files changed

+274
-66
lines changed

15 files changed

+274
-66
lines changed

Diff for: features/dd-sdk-android-rum/api/apiSurface

+5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ data class com.datadog.android.rum.RumConfiguration
8686
fun setSessionListener(RumSessionListener): Builder
8787
fun setInitialResourceIdentifier(com.datadog.android.rum.metric.networksettled.InitialResourceIdentifier): Builder
8888
fun setLastInteractionIdentifier(com.datadog.android.rum.metric.interactiontonextview.LastInteractionIdentifier?): Builder
89+
fun setSlowFrameListenerConfiguration(com.datadog.android.rum.configuration.SlowFrameListenerConfiguration?): Builder
8990
fun build(): RumConfiguration
9091
enum com.datadog.android.rum.RumErrorSource
9192
- NETWORK
@@ -164,6 +165,10 @@ class com.datadog.android.rum._RumInternalProxy
164165
companion object
165166
fun setTelemetryConfigurationEventMapper(RumConfiguration.Builder, com.datadog.android.event.EventMapper<com.datadog.android.telemetry.model.TelemetryConfigurationEvent>): RumConfiguration.Builder
166167
fun setAdditionalConfiguration(RumConfiguration.Builder, Map<String, Any>): RumConfiguration.Builder
168+
data class com.datadog.android.rum.configuration.SlowFrameListenerConfiguration
169+
constructor(Int = DEFAULT_SLOW_FRAME_RECORDS_MAX_AMOUNT, Long = DEFAULT_FROZEN_FRAME_THRESHOLD_NS, Long = DEFAULT_CONTINUOUS_SLOW_FRAME_THRESHOLD_NS, Long = DEFAULT_FREEZE_DURATION_NS, Long = DEFAULT_VIEW_LIFETIME_THRESHOLD_NS)
170+
companion object
171+
val DEFAULT: SlowFrameListenerConfiguration
167172
enum com.datadog.android.rum.configuration.VitalsUpdateFrequency
168173
constructor(Long)
169174
- FREQUENT

Diff for: features/dd-sdk-android-rum/api/dd-sdk-android-rum.api

+17
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public final class com/datadog/android/rum/RumConfiguration$Builder {
111111
public final fun setResourceEventMapper (Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
112112
public final fun setSessionListener (Lcom/datadog/android/rum/RumSessionListener;)Lcom/datadog/android/rum/RumConfiguration$Builder;
113113
public final fun setSessionSampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder;
114+
public final fun setSlowFrameListenerConfiguration (Lcom/datadog/android/rum/configuration/SlowFrameListenerConfiguration;)Lcom/datadog/android/rum/RumConfiguration$Builder;
114115
public final fun setTelemetrySampleRate (F)Lcom/datadog/android/rum/RumConfiguration$Builder;
115116
public final fun setViewEventMapper (Lcom/datadog/android/rum/event/ViewEventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
116117
public final fun setVitalsUpdateFrequency (Lcom/datadog/android/rum/configuration/VitalsUpdateFrequency;)Lcom/datadog/android/rum/RumConfiguration$Builder;
@@ -245,6 +246,22 @@ public final class com/datadog/android/rum/_RumInternalProxy$Companion {
245246
public final fun setTelemetryConfigurationEventMapper (Lcom/datadog/android/rum/RumConfiguration$Builder;Lcom/datadog/android/event/EventMapper;)Lcom/datadog/android/rum/RumConfiguration$Builder;
246247
}
247248

249+
public final class com/datadog/android/rum/configuration/SlowFrameListenerConfiguration {
250+
public static final field Companion Lcom/datadog/android/rum/configuration/SlowFrameListenerConfiguration$Companion;
251+
public fun <init> ()V
252+
public fun <init> (IJJJJ)V
253+
public synthetic fun <init> (IJJJJILkotlin/jvm/internal/DefaultConstructorMarker;)V
254+
public final fun copy (IJJJJ)Lcom/datadog/android/rum/configuration/SlowFrameListenerConfiguration;
255+
public static synthetic fun copy$default (Lcom/datadog/android/rum/configuration/SlowFrameListenerConfiguration;IJJJJILjava/lang/Object;)Lcom/datadog/android/rum/configuration/SlowFrameListenerConfiguration;
256+
public fun equals (Ljava/lang/Object;)Z
257+
public fun hashCode ()I
258+
public fun toString ()Ljava/lang/String;
259+
}
260+
261+
public final class com/datadog/android/rum/configuration/SlowFrameListenerConfiguration$Companion {
262+
public final fun getDEFAULT ()Lcom/datadog/android/rum/configuration/SlowFrameListenerConfiguration;
263+
}
264+
248265
public final class com/datadog/android/rum/configuration/VitalsUpdateFrequency : java/lang/Enum {
249266
public static final field AVERAGE Lcom/datadog/android/rum/configuration/VitalsUpdateFrequency;
250267
public static final field FREQUENT Lcom/datadog/android/rum/configuration/VitalsUpdateFrequency;

Diff for: features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/RumConfiguration.kt

+29
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package com.datadog.android.rum
99
import android.os.Looper
1010
import androidx.annotation.FloatRange
1111
import com.datadog.android.event.EventMapper
12+
import com.datadog.android.rum.configuration.SlowFrameListenerConfiguration
1213
import com.datadog.android.rum.configuration.VitalsUpdateFrequency
1314
import com.datadog.android.rum.event.ViewEventMapper
1415
import com.datadog.android.rum.internal.RumFeature
@@ -283,6 +284,34 @@ data class RumConfiguration internal constructor(
283284
return this
284285
}
285286

287+
/**
288+
* The SlowFramesListener provides statistical data to help identify performance issues related to UI rendering:
289+
*
290+
* - slowFrames: A list of records containing the timestamp and duration of frames where users experience
291+
* jank frames within the given view.
292+
*
293+
* - slowFrameRate: The rate of slow frames encountered during the view's lifetime.
294+
*
295+
* - freezeRate: The rate of freeze occurrences during the view's lifetime.
296+
*
297+
*
298+
*
299+
* This configuration sets the parameters for the SlowFramesListener, which are used to calculate the slow frames array,
300+
* slow frame ratio, and freeze ratio. For additional details, refer to [SlowFrameListenerConfiguration].
301+
*
302+
* Assigning a null value to this property will disable the SlowFramesListener and stop the computation of the
303+
* associated rates.
304+
*
305+
* @param slowFrameListenerConfiguration The configuration to be applied to the SlowFramesListener.
306+
*/
307+
@ExperimentalRumApi
308+
fun setSlowFrameListenerConfiguration(
309+
slowFrameListenerConfiguration: SlowFrameListenerConfiguration?
310+
): Builder {
311+
rumConfig = rumConfig.copy(slowFrameListenerConfiguration = slowFrameListenerConfiguration)
312+
return this
313+
}
314+
286315
/**
287316
* Builds a [RumConfiguration] based on the current state of this Builder.
288317
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
package com.datadog.android.rum.configuration
7+
8+
/**
9+
* The SlowFramesListener provides various statistics to assist in identifying UI performance issues:
10+
*
11+
* - slowFrames: A list of records containing the timestamp and duration of frames where users experience
12+
* jank frames within the given view.
13+
*
14+
* - slowFrameRate: The rate of slow frames encountered during the view's lifetime.
15+
*
16+
* - freezeRate: The rate of freeze occurrences during the view's lifetime.
17+
*
18+
* This class defines thresholds for frame duration to classify frames based on their duration type.
19+
*
20+
* @param maxSlowFramesAmount The maximum number of slow frame records to track for each view.
21+
* The default value is 1000.
22+
*
23+
* @param maxSlowFrameThresholdNs The threshold (in nanoseconds) used to classify a frame as slow.
24+
* Frames with durations exceeding this threshold will not be considered slow and will not affect the
25+
* [com.datadog.android.rum.model.ViewEvent.ViewEventView.slowFramesRate].
26+
* The default value is 700,000,000 ns (700 ms).
27+
*
28+
* @param continuousSlowFrameThresholdNs The threshold (in nanoseconds) used to classify a frame as continuously slow.
29+
* If two consecutive slow frames are recorded and the delay between them is less than this threshold, the previous frame
30+
* record will be updated rather than adding a new one.
31+
* The default value is 16,666,666 ns (approximately 1/60 fps).
32+
*
33+
* @param freezeDurationThreshold The duration (in nanoseconds) used to classify a frame as a freeze frame.
34+
* The cumulative duration of such freezes contributes to the calculation of the
35+
* [com.datadog.android.rum.model.ViewEvent.ViewEventView.freezeRate].
36+
* The default value is 5,000,000,000 ns (5 seconds).
37+
*
38+
* @param minViewLifetimeThresholdNs The minimum lifetime (in nanoseconds) a view must have before it is considered
39+
* for monitoring. The default value is 100,000,000 ns (100 ms).
40+
*/
41+
42+
data class SlowFrameListenerConfiguration(
43+
internal val maxSlowFramesAmount: Int = DEFAULT_SLOW_FRAME_RECORDS_MAX_AMOUNT,
44+
internal val maxSlowFrameThresholdNs: Long = DEFAULT_FROZEN_FRAME_THRESHOLD_NS,
45+
internal val continuousSlowFrameThresholdNs: Long = DEFAULT_CONTINUOUS_SLOW_FRAME_THRESHOLD_NS,
46+
internal val freezeDurationThreshold: Long = DEFAULT_FREEZE_DURATION_NS,
47+
internal val minViewLifetimeThresholdNs: Long = DEFAULT_VIEW_LIFETIME_THRESHOLD_NS
48+
) {
49+
50+
companion object {
51+
/**
52+
* A default configuration of the SlowFrameListenerConfiguration class with all parameters set to their default values.
53+
*/
54+
val DEFAULT: SlowFrameListenerConfiguration = SlowFrameListenerConfiguration()
55+
56+
// Taking into account each Hitch takes 64B in the payload, we can have 64KB max per view event
57+
private const val DEFAULT_SLOW_FRAME_RECORDS_MAX_AMOUNT: Int = 1000
58+
private const val DEFAULT_CONTINUOUS_SLOW_FRAME_THRESHOLD_NS: Long = 16_666_666L // 1/60 fps in nanoseconds
59+
private const val DEFAULT_FROZEN_FRAME_THRESHOLD_NS: Long = 700_000_000 // 700ms
60+
private const val DEFAULT_FREEZE_DURATION_NS: Long = 5_000_000_000L // 5s
61+
private const val DEFAULT_VIEW_LIFETIME_THRESHOLD_NS: Long = 100_000_000L // 100ms
62+
}
63+
}

Diff for: features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/RumFeature.kt

+30-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import com.datadog.android.internal.telemetry.InternalTelemetryEvent
3838
import com.datadog.android.rum.GlobalRumMonitor
3939
import com.datadog.android.rum.RumErrorSource
4040
import com.datadog.android.rum.RumSessionListener
41+
import com.datadog.android.rum.configuration.SlowFrameListenerConfiguration
4142
import com.datadog.android.rum.configuration.VitalsUpdateFrequency
4243
import com.datadog.android.rum.internal.anr.ANRDetectorRunnable
4344
import com.datadog.android.rum.internal.debug.UiRumDebugListener
@@ -152,7 +153,7 @@ internal class RumFeature(
152153
this.appContext = appContext
153154
initialResourceIdentifier = configuration.initialResourceIdentifier
154155
lastInteractionIdentifier = configuration.lastInteractionIdentifier
155-
slowFramesListener = DefaultSlowFramesListener()
156+
156157
dataWriter = createDataWriter(
157158
configuration,
158159
sdkCore as InternalSdkCore
@@ -191,6 +192,8 @@ internal class RumFeature(
191192
initializeANRDetector()
192193
}
193194

195+
initializeSlowFrameListener()
196+
194197
registerTrackingStrategies(appContext)
195198

196199
sessionListener = configuration.sessionListener
@@ -200,6 +203,25 @@ internal class RumFeature(
200203
initialized.set(true)
201204
}
202205

206+
private fun initializeSlowFrameListener() {
207+
val slowFrameListenerConfiguration = configuration.slowFrameListenerConfiguration
208+
slowFramesListener = if (slowFrameListenerConfiguration != null) {
209+
sdkCore.internalLogger.log(
210+
InternalLogger.Level.INFO,
211+
InternalLogger.Target.USER,
212+
{ SLOW_FRAME_MONITORING_ENABLED_MESSAGE }
213+
)
214+
DefaultSlowFramesListener(slowFrameListenerConfiguration)
215+
} else {
216+
sdkCore.internalLogger.log(
217+
InternalLogger.Level.INFO,
218+
InternalLogger.Target.USER,
219+
{ SLOW_FRAME_MONITORING_DISABLED_MESSAGE }
220+
)
221+
NoOpSlowFramesListener()
222+
}
223+
}
224+
203225
override val requestFactory: RequestFactory by lazy {
204226
RumRequestFactory(
205227
customEndpointUrl = configuration.customEndpointUrl,
@@ -571,6 +593,7 @@ internal class RumFeature(
571593
val sessionListener: RumSessionListener,
572594
val initialResourceIdentifier: InitialResourceIdentifier,
573595
val lastInteractionIdentifier: LastInteractionIdentifier?,
596+
val slowFrameListenerConfiguration: SlowFrameListenerConfiguration?,
574597
val additionalConfig: Map<String, Any>,
575598
val trackAnonymousUser: Boolean
576599
)
@@ -618,7 +641,8 @@ internal class RumFeature(
618641
initialResourceIdentifier = TimeBasedInitialResourceIdentifier(),
619642
lastInteractionIdentifier = TimeBasedInteractionIdentifier(),
620643
additionalConfig = emptyMap(),
621-
trackAnonymousUser = true
644+
trackAnonymousUser = true,
645+
slowFrameListenerConfiguration = null
622646
)
623647

624648
internal const val EVENT_MESSAGE_PROPERTY = "message"
@@ -642,6 +666,10 @@ internal class RumFeature(
642666
" where mandatory message field is either missing or has a wrong type."
643667
internal const val DEVELOPER_MODE_SAMPLE_RATE_CHANGED_MESSAGE =
644668
"Developer mode enabled, setting RUM sample rate to 100%."
669+
internal const val SLOW_FRAME_MONITORING_ENABLED_MESSAGE =
670+
"Slow frames monitoring enabled."
671+
internal const val SLOW_FRAME_MONITORING_DISABLED_MESSAGE =
672+
"Slow frames monitoring disabled."
645673
internal const val RUM_FEATURE_NOT_YET_INITIALIZED =
646674
"RUM feature is not initialized yet, you need to register it with a" +
647675
" SDK instance by calling SdkCore#registerFeature method."

Diff for: features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ internal open class RumViewScope(
915915
}
916916

917917
val uiSlownessReport = slowFramesListener.resolveReport(viewId)
918-
val freezeRate = uiSlownessReport.anrDurationRate(stoppedNanos)
918+
val freezeRate = uiSlownessReport.freezeFramesRate(stoppedNanos)
919919
val slowFramesRate = uiSlownessReport.slowFramesRate(stoppedNanos)
920920
val slowFrames = uiSlownessReport.slowFramesRecords.map {
921921
ViewEvent.SlowFrame(

Diff for: features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/state/ViewUIPerformanceReport.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ internal data class ViewUIPerformanceReport(
4141
else -> 0.0
4242
}
4343

44-
fun anrDurationRate(viewEndedTimeStamp: Long): Double = when {
44+
fun freezeFramesRate(viewEndedTimeStamp: Long): Double = when {
4545
viewEndedTimeStamp - viewStartedTimeStamp <= minViewLifetimeThresholdNs -> 0.0
4646
else -> max(
4747
0.0,

Diff for: features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt

+12-22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package com.datadog.android.rum.internal.metric.slowframes
88
import androidx.annotation.MainThread
99
import androidx.annotation.WorkerThread
1010
import androidx.metrics.performance.FrameData
11+
import com.datadog.android.rum.configuration.SlowFrameListenerConfiguration
1112
import com.datadog.android.rum.internal.domain.FrameMetricsData
1213
import com.datadog.android.rum.internal.domain.state.SlowFrameRecord
1314
import com.datadog.android.rum.internal.domain.state.ViewUIPerformanceReport
@@ -24,11 +25,7 @@ internal interface SlowFramesListener : FrameStateListener {
2425
}
2526

2627
internal class DefaultSlowFramesListener(
27-
internal val maxSlowFramesAmount: Int = DEFAULT_SLOW_FRAME_RECORDS_MAX_AMOUNT,
28-
internal val frozenFrameThresholdNs: Long = DEFAULT_FROZEN_FRAME_THRESHOLD_NS,
29-
internal val continuousSlowFrameThresholdNs: Long = DEFAULT_CONTINUOUS_SLOW_FRAME_THRESHOLD_NS,
30-
internal val anrDuration: Long = DEFAULT_ANR_DURATION_NS,
31-
internal val minViewLifetimeThresholdNs: Long = DEFAULT_VIEW_LIFETIME_THRESHOLD_NS
28+
internal val configuration: SlowFrameListenerConfiguration
3229
) : SlowFramesListener {
3330

3431
@Volatile
@@ -50,8 +47,8 @@ internal class DefaultSlowFramesListener(
5047
@Suppress("UnsafeThirdPartyFunctionCall") // can't have NPE here
5148
val report = slowFramesRecords.remove(viewId) ?: ViewUIPerformanceReport(
5249
System.nanoTime(),
53-
maxSlowFramesAmount,
54-
minViewLifetimeThresholdNs
50+
configuration.maxSlowFramesAmount,
51+
configuration.minViewLifetimeThresholdNs
5552
)
5653

5754
// making sure that report is not partially updated
@@ -73,7 +70,7 @@ internal class DefaultSlowFramesListener(
7370
// Updating frames statistics
7471
report.totalFramesDurationNs += frameDurationNs
7572

76-
if (frameDurationNs > frozenFrameThresholdNs || !volatileFrameData.isJank) {
73+
if (frameDurationNs > configuration.maxSlowFrameThresholdNs || !volatileFrameData.isJank) {
7774
// Frame duration is too big to be considered as a slow frame or not jank
7875
return
7976
}
@@ -83,7 +80,9 @@ internal class DefaultSlowFramesListener(
8380
val delaySinceLastUpdate = frameStartedTimestampNs -
8481
(previousSlowFrameRecord?.startTimestampNs ?: frameStartedTimestampNs)
8582

86-
if (previousSlowFrameRecord == null || delaySinceLastUpdate > continuousSlowFrameThresholdNs) {
83+
if (previousSlowFrameRecord == null ||
84+
delaySinceLastUpdate > configuration.continuousSlowFrameThresholdNs
85+
) {
8786
// No previous slow frame record or amount of time since the last update
8887
// is significant enough to consider it idle - adding a new slow frame record.
8988
if (frameDurationNs > 0) {
@@ -96,7 +95,7 @@ internal class DefaultSlowFramesListener(
9695
// It's a continuous slow frame – increasing duration
9796
previousSlowFrameRecord.durationNs = min(
9897
previousSlowFrameRecord.durationNs + frameDurationNs,
99-
frozenFrameThresholdNs - 1
98+
configuration.maxSlowFrameThresholdNs - 1
10099
)
101100
}
102101
}
@@ -105,7 +104,7 @@ internal class DefaultSlowFramesListener(
105104
@WorkerThread
106105
override fun onAddLongTask(durationNs: Long) {
107106
val view = currentViewId
108-
if (durationNs >= anrDuration && view != null) {
107+
if (durationNs >= configuration.freezeDurationThreshold && view != null) {
109108
val report = getViewPerformanceReport(view)
110109
synchronized(report) { report.anrDurationNs += durationNs }
111110
}
@@ -119,17 +118,8 @@ internal class DefaultSlowFramesListener(
119118
private fun getViewPerformanceReport(viewId: String) = slowFramesRecords.getOrPut(viewId) {
120119
ViewUIPerformanceReport(
121120
currentViewStartedTimeStampNs,
122-
maxSlowFramesAmount,
123-
minimumViewLifetimeThresholdNs = minViewLifetimeThresholdNs
121+
configuration.maxSlowFramesAmount,
122+
minimumViewLifetimeThresholdNs = configuration.minViewLifetimeThresholdNs
124123
)
125124
}
126-
127-
companion object {
128-
// Taking into account each Hitch takes 64B in the payload, we can have 64KB max per view event
129-
private const val DEFAULT_SLOW_FRAME_RECORDS_MAX_AMOUNT: Int = 1000
130-
private const val DEFAULT_CONTINUOUS_SLOW_FRAME_THRESHOLD_NS: Long = 16_666_666L // 1/60 fps in nanoseconds
131-
private const val DEFAULT_FROZEN_FRAME_THRESHOLD_NS: Long = 700_000_000 // 700ms
132-
private const val DEFAULT_ANR_DURATION_NS: Long = 5_000_000_000L // 5s
133-
private const val DEFAULT_VIEW_LIFETIME_THRESHOLD_NS: Long = 100_000_000L // 100ms
134-
}
135125
}

0 commit comments

Comments
 (0)