Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SR always listens and reacts to RUM session state #1539

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,21 +158,25 @@ internal class RumSessionScope(
} else if (isTimedOut) {
renewSession(nanoTime)
}

updateSessionStateForSessionReplay(sessionState, sessionId)
}

private fun renewSession(nanoTime: Long) {
val keepSession = random.nextFloat() < sampleRate.percent()
sessionState = if (keepSession) State.TRACKED else State.NOT_TRACKED
sessionId = UUID.randomUUID().toString()
sessionStartNs.set(nanoTime)
sdkCore.updateFeatureContext(Feature.RUM_FEATURE_NAME) {
it.putAll(getRumContext().toMap())
}
sessionListener?.onSessionStarted(sessionId, !keepSession)
}

private fun updateSessionStateForSessionReplay(state: State, sessionId: String) {
val keepSession = (state == State.TRACKED)
sdkCore.getFeature(Feature.SESSION_REPLAY_FEATURE_NAME)?.sendEvent(
mapOf(
SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to RUM_SESSION_RENEWED_BUS_MESSAGE,
RUM_KEEP_SESSION_BUS_MESSAGE_KEY to keepSession
RUM_KEEP_SESSION_BUS_MESSAGE_KEY to keepSession,
RUM_SESSION_ID_BUS_MESSAGE_KEY to sessionId
)
)
}
Expand All @@ -184,6 +188,7 @@ internal class RumSessionScope(
internal const val SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY = "type"
internal const val RUM_SESSION_RENEWED_BUS_MESSAGE = "rum_session_renewed"
internal const val RUM_KEEP_SESSION_BUS_MESSAGE_KEY = "keepSession"
internal const val RUM_SESSION_ID_BUS_MESSAGE_KEY = "sessionId"
internal val DEFAULT_SESSION_INACTIVITY_NS = TimeUnit.MINUTES.toNanos(15)
internal val DEFAULT_SESSION_MAX_DURATION_NS = TimeUnit.HOURS.toNanos(4)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -819,16 +819,96 @@ internal class RumSessionScopeTest {

// region Session Replay Event Bus

@Test
fun `𝕄 notify Session Replay feature 𝕎 new interaction event received`(
forge: Forge
) {
// Given
val fakeInteractionEvent1 = forge.interactiveRumRawEvent()
val fakeInteractionEvent2 = forge.interactiveRumRawEvent()
testedScope.handleEvent(fakeInteractionEvent1, mockWriter)
testedScope.handleEvent(fakeInteractionEvent2, mockWriter)

// Then
val argumentCaptor = argumentCaptor<Any>()
verify(mockSessionReplayFeatureScope, times(2))
.sendEvent(argumentCaptor.capture())
assertThat(argumentCaptor.firstValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
testedScope.getRumContext().sessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
testedScope.getRumContext().sessionId
)
)
}

@Test
fun `𝕄 notify Session Replay feature 𝕎 new non-interaction event received`(
forge: Forge
) {
// Given
val fakeNonInteractionEvent1 = forge.anyRumEvent(
excluding = listOf(
RumRawEvent.StartView::class.java,
RumRawEvent.StartAction::class.java
)
)
val fakeNonInteractionEvent2 = forge.anyRumEvent(
excluding = listOf(
RumRawEvent.StartView::class.java,
RumRawEvent.StartAction::class.java
)
)
testedScope.handleEvent(fakeNonInteractionEvent1, mockWriter)
testedScope.handleEvent(fakeNonInteractionEvent2, mockWriter)

// Then
val argumentCaptor = argumentCaptor<Any>()
verify(mockSessionReplayFeatureScope, times(2))
.sendEvent(argumentCaptor.capture())
assertThat(argumentCaptor.firstValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
testedScope.getRumContext().sessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
testedScope.getRumContext().sessionId
)
)
}

@Test
fun `𝕄 notify Session Replay feature 𝕎 session is updated {tracked, timed out}`(
forge: Forge
) {
// Given
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val firstSessionId = testedScope.getRumContext().sessionId

// When
Thread.sleep(TEST_MAX_DURATION_MS)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val secondSessionId = testedScope.getRumContext().sessionId

// Then
val argumentCaptor = argumentCaptor<Any>()
Expand All @@ -838,14 +918,16 @@ internal class RumSessionScopeTest {
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId
)
)
}
Expand All @@ -856,10 +938,12 @@ internal class RumSessionScopeTest {
) {
// Given
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val firstSessionId = testedScope.getRumContext().sessionId

// When
Thread.sleep(TEST_INACTIVITY_MS)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val secondSessionId = testedScope.getRumContext().sessionId

// Then
val argumentCaptor = argumentCaptor<Any>()
Expand All @@ -869,14 +953,18 @@ internal class RumSessionScopeTest {
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
firstSessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
secondSessionId
)
)
}
Expand All @@ -887,27 +975,42 @@ internal class RumSessionScopeTest {
) {
// Given
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val firstSessionId = testedScope.getRumContext().sessionId

// When
testedScope.handleEvent(RumRawEvent.ResetSession(), mockWriter)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val secondSessionId = testedScope.getRumContext().sessionId

// Then
val argumentCaptor = argumentCaptor<Any>()
verify(mockSessionReplayFeatureScope, times(2))
verify(mockSessionReplayFeatureScope, times(3))
.sendEvent(argumentCaptor.capture())
assertThat(argumentCaptor.firstValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
firstSessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
secondSessionId
)
)
assertThat(argumentCaptor.thirdValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to true,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to
secondSessionId
)
)
}
Expand All @@ -919,10 +1022,12 @@ internal class RumSessionScopeTest {
// Given
initializeTestedScope(0f)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val firstSessionId = testedScope.getRumContext().sessionId

// When
Thread.sleep(TEST_MAX_DURATION_MS)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val secondSessionId = testedScope.getRumContext().sessionId

// Then
val argumentCaptor = argumentCaptor<Any>()
Expand All @@ -932,14 +1037,17 @@ internal class RumSessionScopeTest {
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId

)
)
}
Expand All @@ -951,10 +1059,12 @@ internal class RumSessionScopeTest {
// Given
initializeTestedScope(0f)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val firstSessionId = testedScope.getRumContext().sessionId

// When
Thread.sleep(TEST_INACTIVITY_MS)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val secondSessionId = testedScope.getRumContext().sessionId

// Then
val argumentCaptor = argumentCaptor<Any>()
Expand All @@ -964,14 +1074,16 @@ internal class RumSessionScopeTest {
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId
)
)
}
Expand All @@ -983,27 +1095,39 @@ internal class RumSessionScopeTest {
// Given
initializeTestedScope(0f)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val firstSessionId = testedScope.getRumContext().sessionId

// When
testedScope.handleEvent(RumRawEvent.ResetSession(), mockWriter)
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
val secondSessionId = testedScope.getRumContext().sessionId

// Then
val argumentCaptor = argumentCaptor<Any>()
verify(mockSessionReplayFeatureScope, times(2))
verify(mockSessionReplayFeatureScope, times(3))
.sendEvent(argumentCaptor.capture())
assertThat(argumentCaptor.firstValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to firstSessionId
)
)
assertThat(argumentCaptor.secondValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId
)
)
assertThat(argumentCaptor.thirdValue).isEqualTo(
mapOf(
RumSessionScope.SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY to
RumSessionScope.RUM_SESSION_RENEWED_BUS_MESSAGE,
RumSessionScope.RUM_KEEP_SESSION_BUS_MESSAGE_KEY to false,
RumSessionScope.RUM_SESSION_ID_BUS_MESSAGE_KEY to secondSessionId
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.datadog.android.sessionreplay.internal.storage.SessionReplayRecordWri
import com.datadog.android.sessionreplay.internal.time.SessionReplayTimeProvider
import java.util.Locale
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference

/**
* Session Replay feature class, which needs to be registered with Datadog SDK instance.
Expand All @@ -43,6 +44,8 @@ internal class SessionReplayFeature constructor(
private val sessionReplayRecorderProvider: (RecordWriter, Application) -> Recorder
) : StorageBackedFeature, FeatureEventReceiver {

private val currentRumSessionId = AtomicReference<String>()

internal constructor(
sdkCore: FeatureSdkCore,
customEndpointUrl: String?,
Expand Down Expand Up @@ -155,8 +158,9 @@ internal class SessionReplayFeature constructor(

private fun checkStatusAndApplySample(sessionMetadata: Map<*, *>) {
val keepSession = sessionMetadata[RUM_KEEP_SESSION_BUS_MESSAGE_KEY] as? Boolean
val sessionId = sessionMetadata[RUM_SESSION_ID_BUS_MESSAGE_KEY] as? String

if (keepSession == null) {
if (keepSession == null || sessionId == null) {
sdkCore.internalLogger.log(
InternalLogger.Level.WARN,
InternalLogger.Target.USER,
Expand All @@ -165,6 +169,11 @@ internal class SessionReplayFeature constructor(
return
}

if (currentRumSessionId.get() == sessionId) {
// we already handled this session
return
}

if (keepSession && rateBasedSampler.sample()) {
startRecording()
} else {
Expand All @@ -175,6 +184,8 @@ internal class SessionReplayFeature constructor(
)
stopRecording()
}

currentRumSessionId.set(sessionId)
}

/**
Expand Down Expand Up @@ -246,5 +257,6 @@ internal class SessionReplayFeature constructor(
const val SESSION_REPLAY_BUS_MESSAGE_TYPE_KEY = "type"
const val RUM_SESSION_RENEWED_BUS_MESSAGE = "rum_session_renewed"
const val RUM_KEEP_SESSION_BUS_MESSAGE_KEY = "keepSession"
const val RUM_SESSION_ID_BUS_MESSAGE_KEY = "sessionId"
}
}
Loading