Skip to content

Commit d188b59

Browse files
authored
Merge pull request #2650 from DataDog/nogorodnikov/rum-9977/update-rum-feature-context-once
RUM-9977: Update RUM feature context only after event processing completion
2 parents 503d4fc + e27d65f commit d188b59

File tree

12 files changed

+517
-1001
lines changed

12 files changed

+517
-1001
lines changed

detekt_custom.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,8 +991,9 @@ datadog:
991991
- "kotlin.collections.MutableList.add(com.datadog.android.api.InternalLogger.Target)"
992992
- "kotlin.collections.MutableList.add(com.datadog.android.core.internal.persistence.Batch)"
993993
- "kotlin.collections.MutableList.add(com.datadog.android.core.internal.persistence.tlvformat.TLVBlock)"
994-
- "kotlin.collections.MutableList.add(com.datadog.android.plugin.DatadogPlugin)"
995994
- "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.domain.scope.RumScope)"
995+
- "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.domain.scope.RumSessionScope)"
996+
- "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.domain.scope.RumViewScope)"
996997
- "kotlin.collections.MutableList.add(com.datadog.android.rum.internal.vitals.FrameStateListener)"
997998
- "kotlin.collections.MutableList.add(com.datadog.android.rum.model.ActionEvent.Type)"
998999
- "kotlin.collections.MutableList.add(com.datadog.android.sessionreplay.compose.internal.data.Parameter)"

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScope.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import androidx.annotation.WorkerThread
1111
import com.datadog.android.api.InternalLogger
1212
import com.datadog.android.api.context.DatadogContext
1313
import com.datadog.android.api.feature.EventWriteScope
14-
import com.datadog.android.api.feature.Feature
1514
import com.datadog.android.api.storage.DataWriter
1615
import com.datadog.android.core.InternalSdkCore
1716
import com.datadog.android.core.internal.net.FirstPartyHostHeaderTypeResolver
@@ -46,7 +45,7 @@ internal class RumApplicationScope(
4645

4746
private var rumContext = RumContext(applicationId = applicationId)
4847

49-
internal val childScopes: MutableList<RumScope> = mutableListOf(
48+
internal val childScopes = mutableListOf<RumSessionScope>(
5049
RumSessionScope(
5150
this,
5251
sdkCore,
@@ -67,9 +66,17 @@ internal class RumApplicationScope(
6766
)
6867
)
6968

70-
val activeSession: RumScope?
69+
val activeSession: RumSessionScope?
7170
get() {
72-
return childScopes.find { it.isActive() }
71+
val activeSessions = childScopes.filter { it.isActive() }
72+
if (activeSessions.size > 1) {
73+
sdkCore.internalLogger.log(
74+
InternalLogger.Level.ERROR,
75+
InternalLogger.Target.MAINTAINER,
76+
{ MULTIPLE_ACTIVE_SESSIONS_ERROR }
77+
)
78+
}
79+
return activeSessions.lastOrNull()
7380
}
7481

7582
private var lastActiveViewInfo: RumViewInfo? = null
@@ -94,10 +101,6 @@ internal class RumApplicationScope(
94101
val isInteraction = (event is RumRawEvent.StartView) || (event is RumRawEvent.StartAction)
95102
if (activeSession == null && isInteraction) {
96103
startNewSession(event, datadogContext, writeScope, writer)
97-
} else if (event is RumRawEvent.StopSession) {
98-
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) {
99-
it.putAll(getRumContext().toMap())
100-
}
101104
}
102105

103106
if (event !is RumRawEvent.SdkInit && !isAppStartedEventSent) {
@@ -185,7 +188,7 @@ internal class RumApplicationScope(
185188
sdkCore.internalLogger.log(
186189
InternalLogger.Level.ERROR,
187190
InternalLogger.Target.TELEMETRY,
188-
{ MULTIPLE_ACTIVE_SESSIONS_ERROR }
191+
{ MULTIPLE_ACTIVE_SESSIONS_SESSION_START_ERROR }
189192
)
190193
}
191194
}
@@ -224,7 +227,9 @@ internal class RumApplicationScope(
224227
// endregion
225228

226229
companion object {
227-
internal const val MULTIPLE_ACTIVE_SESSIONS_ERROR = "Application has multiple active " +
230+
internal const val MULTIPLE_ACTIVE_SESSIONS_SESSION_START_ERROR = "Application has multiple active " +
228231
"sessions when starting a new session"
232+
internal const val MULTIPLE_ACTIVE_SESSIONS_ERROR = "Application has multiple active " +
233+
"sessions, this shouldn't happen."
229234
}
230235
}

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumSessionScope.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ internal class RumSessionScope(
6262
private val noOpWriter = NoOpDataWriter<Any>()
6363

6464
@Suppress("LongParameterList")
65-
internal var childScope: RumScope? = RumViewManagerScope(
65+
internal var childScope: RumViewManagerScope? = RumViewManagerScope(
6666
this,
6767
sdkCore,
6868
sessionEndedMetricDispatcher,
@@ -80,11 +80,12 @@ internal class RumSessionScope(
8080
lastInteractionIdentifier
8181
)
8282

83-
init {
84-
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) {
85-
it.putAll(getRumContext().toMap())
83+
internal val activeView: RumViewScope?
84+
get() = if (isActive() && childScope != null) {
85+
childScope?.activeView
86+
} else {
87+
null
8688
}
87-
}
8889

8990
enum class State(val asString: String) {
9091
NOT_TRACKED("NOT_TRACKED"),
@@ -135,7 +136,8 @@ internal class RumSessionScope(
135136
val actualWriter = if (sessionState == State.TRACKED) writer else noOpWriter
136137

137138
if (event !is RumRawEvent.SdkInit) {
138-
childScope = childScope?.handleEvent(event, datadogContext, writeScope, actualWriter)
139+
childScope = childScope
140+
?.handleEvent(event, datadogContext, writeScope, actualWriter) as? RumViewManagerScope
139141
}
140142

141143
return if (isSessionComplete()) {

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScope.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,25 @@ internal class RumViewManagerScope(
5858
lastInteractionIdentifier = lastInteractionIdentifier
5959
)
6060

61-
internal val childrenScopes = mutableListOf<RumScope>()
61+
internal val childrenScopes = mutableListOf<RumViewScope>()
62+
63+
internal val activeView: RumViewScope?
64+
get() {
65+
return if (isActive()) {
66+
val activeViews = childrenScopes.filter { it.isActive() }
67+
if (activeViews.size > 1) {
68+
sdkCore.internalLogger.log(
69+
InternalLogger.Level.ERROR,
70+
InternalLogger.Target.MAINTAINER,
71+
{ "Multiple views are active at the same time, this shouldn't happen." }
72+
)
73+
}
74+
activeViews.lastOrNull()
75+
} else {
76+
null
77+
}
78+
}
79+
6280
internal var stopped = false
6381
private var lastStoppedViewTime: Time? = null
6482

@@ -419,6 +437,9 @@ internal class RumViewManagerScope(
419437
internal const val NO_ACTIVE_VIEW_FOR_LOADING_TIME_WARNING_MESSAGE =
420438
"No active view found to add the loading time."
421439

440+
internal const val MULTIPLE_ACTIVE_VIEWS_ERROR =
441+
"Multiple views are active at the same time, this shouldn't happen."
442+
422443
internal val THREE_SECONDS_GAP_NS = TimeUnit.SECONDS.toNanos(3)
423444
}
424445
}

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

Lines changed: 9 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,6 @@ internal open class RumViewScope(
163163
// endregion
164164

165165
init {
166-
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) {
167-
it.putAll(getRumContext().toMap())
168-
}
169-
170166
cpuVitalMonitor.register(cpuVitalListener)
171167
memoryVitalMonitor.register(memoryVitalListener)
172168
frameRateVitalMonitor.register(frameRateVitalListener)
@@ -358,38 +354,6 @@ internal open class RumViewScope(
358354
val shouldStop = (event.key.id == key.id)
359355
if (shouldStop && !stopped) {
360356
stopScope(event, datadogContext, writeScope, writer) {
361-
// we should not reset the timestamp offset here as due to async nature of feature context update
362-
// we still need a stable value for the view timestamp offset for WebView RUM events timestamp
363-
// correction
364-
val newRumContext = getRumContext().copy(
365-
viewType = RumViewType.NONE,
366-
viewId = null,
367-
viewName = null,
368-
viewUrl = null,
369-
actionId = null
370-
)
371-
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) { currentRumContext ->
372-
val canUpdate = when {
373-
currentRumContext[RumContext.SESSION_ID] != this.sessionId -> {
374-
// we have a new session, so whatever is in the Global context is
375-
// not valid anyway
376-
true
377-
}
378-
379-
currentRumContext[RumContext.VIEW_ID] == this.viewId -> true
380-
else -> false
381-
}
382-
if (canUpdate) {
383-
currentRumContext.clear()
384-
currentRumContext.putAll(newRumContext.toMap())
385-
} else {
386-
sdkCore.internalLogger.log(
387-
InternalLogger.Level.DEBUG,
388-
InternalLogger.Target.MAINTAINER,
389-
{ RUM_CONTEXT_UPDATE_IGNORED_AT_STOP_VIEW_MESSAGE }
390-
)
391-
}
392-
}
393357
eventAttributes.putAll(event.attributes)
394358
}
395359
}
@@ -432,16 +396,14 @@ internal open class RumViewScope(
432396
}
433397
}
434398

435-
updateActiveActionScope(
436-
RumActionScope.fromEvent(
437-
this,
438-
sdkCore,
439-
event,
440-
serverTimeOffsetInMs,
441-
featuresContextResolver,
442-
trackFrustrations,
443-
sampleRate
444-
)
399+
activeActionScope = RumActionScope.fromEvent(
400+
this,
401+
sdkCore,
402+
event,
403+
serverTimeOffsetInMs,
404+
featuresContextResolver,
405+
trackFrustrations,
406+
sampleRate
445407
)
446408
pendingActionCount++
447409
}
@@ -707,31 +669,7 @@ internal open class RumViewScope(
707669
if (currentAction != null) {
708670
val updatedAction = currentAction.handleEvent(event, datadogContext, writeScope, writer)
709671
if (updatedAction == null) {
710-
updateActiveActionScope(null)
711-
}
712-
}
713-
}
714-
715-
private fun updateActiveActionScope(scope: RumScope?) {
716-
activeActionScope = scope
717-
// update the Rum Context to make it available for Logs/Trace bundling
718-
val newRumContext = getRumContext()
719-
720-
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) { currentRumContext ->
721-
val canUpdate = when {
722-
currentRumContext[RumContext.SESSION_ID] != sessionId -> true
723-
currentRumContext[RumContext.VIEW_ID] == viewId -> true
724-
else -> false
725-
}
726-
if (canUpdate) {
727-
currentRumContext.clear()
728-
currentRumContext.putAll(newRumContext.toMap())
729-
} else {
730-
sdkCore.internalLogger.log(
731-
InternalLogger.Level.DEBUG,
732-
InternalLogger.Target.MAINTAINER,
733-
{ RUM_CONTEXT_UPDATE_IGNORED_AT_ACTION_UPDATE_MESSAGE }
734-
)
672+
activeActionScope = null
735673
}
736674
}
737675
}
@@ -1476,13 +1414,6 @@ internal open class RumViewScope(
14761414
internal const val ACTION_DROPPED_WARNING = "RUM Action (%s on %s) was dropped, because" +
14771415
" another action is still active for the same view"
14781416

1479-
internal const val RUM_CONTEXT_UPDATE_IGNORED_AT_STOP_VIEW_MESSAGE =
1480-
"Trying to update global RUM context when StopView event arrived, but the context" +
1481-
" doesn't reference this view."
1482-
internal const val RUM_CONTEXT_UPDATE_IGNORED_AT_ACTION_UPDATE_MESSAGE =
1483-
"Trying to update active action in the global RUM context, but the context" +
1484-
" doesn't reference this view."
1485-
14861417
internal val FROZEN_FRAME_THRESHOLD_NS = TimeUnit.MILLISECONDS.toNanos(700)
14871418
internal const val SLOW_RENDERED_THRESHOLD_FPS = 55
14881419
internal const val ZERO_DURATION_WARNING_MESSAGE = "The computed duration for the " +

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/monitor/DatadogRumMonitor.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,8 @@ import com.datadog.android.rum.internal.domain.asTime
3838
import com.datadog.android.rum.internal.domain.event.ResourceTiming
3939
import com.datadog.android.rum.internal.domain.scope.RumApplicationScope
4040
import com.datadog.android.rum.internal.domain.scope.RumRawEvent
41-
import com.datadog.android.rum.internal.domain.scope.RumScope
4241
import com.datadog.android.rum.internal.domain.scope.RumScopeKey
4342
import com.datadog.android.rum.internal.domain.scope.RumSessionScope
44-
import com.datadog.android.rum.internal.domain.scope.RumViewManagerScope
4543
import com.datadog.android.rum.internal.domain.scope.RumViewScope
4644
import com.datadog.android.rum.internal.metric.SessionMetricDispatcher
4745
import com.datadog.android.rum.internal.metric.slowframes.SlowFramesListener
@@ -80,7 +78,7 @@ internal class DatadogRumMonitor(
8078
slowFramesListener: SlowFramesListener?
8179
) : RumMonitor, AdvancedRumMonitor {
8280

83-
internal var rootScope: RumScope = RumApplicationScope(
81+
internal var rootScope = RumApplicationScope(
8482
applicationId,
8583
sdkCore,
8684
sampleRate,
@@ -120,8 +118,7 @@ internal class DatadogRumMonitor(
120118
"Get current session ID",
121119
sdkCore.internalLogger
122120
) {
123-
val activeSessionId = (rootScope as? RumApplicationScope)
124-
?.activeSession
121+
val activeSessionId = rootScope.activeSession
125122
?.getRumContext()
126123
?.let {
127124
val sessionId = it.sessionId
@@ -682,6 +679,7 @@ internal class DatadogRumMonitor(
682679
val (datadogContext, eventWriteScope) = writeContext
683680
@Suppress("ThreadSafety") // Crash handling, can't delegate to another thread
684681
rootScope.handleEvent(event, datadogContext, eventWriteScope, writer)
682+
updateFeatureContext()
685683
} else {
686684
sdkCore.internalLogger.log(
687685
InternalLogger.Level.WARN,
@@ -700,6 +698,7 @@ internal class DatadogRumMonitor(
700698
executorService.executeSafe("Rum event handling", sdkCore.internalLogger) {
701699
synchronized(rootScope) {
702700
rootScope.handleEvent(event, datadogContext, writeScope, writer)
701+
updateFeatureContext()
703702
notifyDebugListenerWithState()
704703
}
705704
handler.postDelayed(keepAliveRunnable, KEEP_ALIVE_MS)
@@ -733,15 +732,24 @@ internal class DatadogRumMonitor(
733732
}
734733
}
735734

735+
private fun updateFeatureContext() {
736+
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) {
737+
val activeSession = rootScope.activeSession
738+
val context = activeSession?.activeView?.getRumContext()
739+
?: activeSession?.getRumContext()
740+
?: rootScope.getRumContext()
741+
it.putAll(context.toMap())
742+
}
743+
}
744+
736745
internal fun stopKeepAliveCallback() {
737746
handler.removeCallbacks(keepAliveRunnable)
738747
}
739748

740749
internal fun notifyDebugListenerWithState() {
741750
debugListener?.let {
742-
val applicationScope = rootScope as? RumApplicationScope
743-
val sessionScope = applicationScope?.activeSession as? RumSessionScope
744-
val viewManagerScope = sessionScope?.childScope as? RumViewManagerScope
751+
val sessionScope = rootScope.activeSession
752+
val viewManagerScope = sessionScope?.childScope
745753
if (viewManagerScope != null) {
746754
it.onReceiveRumActiveViews(
747755
viewManagerScope.childrenScopes

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/RumTest.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import com.datadog.android.api.feature.FeatureSdkCore
1313
import com.datadog.android.core.InternalSdkCore
1414
import com.datadog.android.core.sampling.RateBasedSampler
1515
import com.datadog.android.rum.internal.RumFeature
16-
import com.datadog.android.rum.internal.domain.scope.RumApplicationScope
1716
import com.datadog.android.rum.internal.monitor.DatadogRumMonitor
1817
import com.datadog.android.rum.internal.monitor.NoOpAdvancedRumMonitor
1918
import com.datadog.android.rum.internal.net.RumRequestFactory
@@ -135,14 +134,12 @@ internal class RumTest {
135134
// Then
136135
val monitor = GlobalRumMonitor.get(mockSdkCore)
137136
check(monitor is DatadogRumMonitor)
138-
assertThat(monitor.rootScope).isInstanceOf(RumApplicationScope::class.java)
139137
assertThat(monitor.rootScope)
140138
.overridingErrorMessage(
141139
"Expecting root scope to have applicationId ${fakeRumConfiguration.applicationId}"
142140
)
143141
.matches {
144-
(it as RumApplicationScope)
145-
.getRumContext()
142+
it.getRumContext()
146143
.applicationId == fakeRumConfiguration.applicationId
147144
}
148145
assertThat(monitor.handler.looper).isSameAs(Looper.getMainLooper())
@@ -158,7 +155,7 @@ internal class RumTest {
158155

159156
assertThat(telemetrySampler.getSampleRate())
160157
.isEqualTo(fakeRumConfiguration.featureConfiguration.telemetrySampleRate)
161-
val rumApplicationScope = monitor.rootScope as RumApplicationScope
158+
val rumApplicationScope = monitor.rootScope
162159
assertThat(rumApplicationScope.initialResourceIdentifier)
163160
.isSameAs(fakeRumConfiguration.featureConfiguration.initialResourceIdentifier)
164161
assertThat(rumApplicationScope.lastInteractionIdentifier)

0 commit comments

Comments
 (0)