Skip to content

Commit

Permalink
Merge pull request #1755 from DataDog/xgouchet/RUM-1336/session_start…
Browse files Browse the repository at this point in the history
…_reason

RUM-1336 Add session start reason to events
  • Loading branch information
xgouchet authored Dec 11, 2023
2 parents b3779e4 + 040cd6a commit 2f894e9
Show file tree
Hide file tree
Showing 17 changed files with 404 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal data class RumContext(
val viewUrl: String? = null,
val actionId: String? = null,
val sessionState: RumSessionScope.State = RumSessionScope.State.NOT_TRACKED,
val sessionStartReason: RumSessionScope.StartReason = RumSessionScope.StartReason.USER_APP_LAUNCH,
val viewType: RumViewScope.RumViewType = RumViewScope.RumViewType.NONE,
val syntheticsTestId: String? = null,
val syntheticsResultId: String? = null
Expand All @@ -28,7 +29,9 @@ internal data class RumContext(
return mapOf(
APPLICATION_ID to applicationId,
SESSION_ID to sessionId,
SESSION_ACTIVE to isSessionActive,
SESSION_STATE to sessionState.asString,
SESSION_START_REASON to sessionStartReason.asString,
VIEW_ID to viewId,
VIEW_NAME to viewName,
VIEW_URL to viewUrl,
Expand All @@ -46,7 +49,9 @@ internal data class RumContext(
// literal) from other modules
const val APPLICATION_ID = "application_id"
const val SESSION_ID = "session_id"
const val SESSION_ACTIVE = "session_active"
const val SESSION_STATE = "session_state"
const val SESSION_START_REASON = "session_start_reason"
const val VIEW_ID = "view_id"
const val VIEW_NAME = "view_name"
const val VIEW_URL = "view_url"
Expand All @@ -58,8 +63,13 @@ internal data class RumContext(
fun fromFeatureContext(featureContext: Map<String, Any?>): RumContext {
val applicationId = featureContext[APPLICATION_ID] as? String
val sessionId = featureContext[SESSION_ID] as? String
val sessionState =
RumSessionScope.State.fromString(featureContext[SESSION_STATE] as? String)
val isSessionActive = featureContext[SESSION_ACTIVE] as? Boolean
val sessionState = RumSessionScope.State.fromString(
featureContext[SESSION_STATE] as? String
)
val sessionStartReason = RumSessionScope.StartReason.fromString(
featureContext[SESSION_START_REASON] as? String
)
val viewId = featureContext[VIEW_ID] as? String
val viewName = featureContext[VIEW_NAME] as? String
val viewUrl = featureContext[VIEW_URL] as? String
Expand All @@ -71,7 +81,9 @@ internal data class RumContext(
return RumContext(
applicationId = applicationId ?: NULL_UUID,
sessionId = sessionId ?: NULL_UUID,
isSessionActive = isSessionActive ?: false,
sessionState = sessionState ?: RumSessionScope.State.NOT_TRACKED,
sessionStartReason = sessionStartReason ?: RumSessionScope.StartReason.USER_APP_LAUNCH,
viewId = viewId,
viewName = viewName,
viewUrl = viewUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,10 @@ internal class RumActionScope(
),
context = ActionEvent.Context(additionalProperties = attributes),
dd = ActionEvent.Dd(
session = ActionEvent.DdSession(plan = ActionEvent.Plan.PLAN_1),
session = ActionEvent.DdSession(
plan = ActionEvent.Plan.PLAN_1,
sessionPrecondition = rumContext.sessionStartReason.toActionSessionPrecondition()
),
configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate)
),
connectivity = networkInfo.toActionConnectivity(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import com.datadog.android.rum.model.ResourceEvent
import com.datadog.android.rum.model.ViewEvent
import java.util.Locale

// region Resource.Method conversion

internal fun RumResourceMethod.toResourceMethod(): ResourceEvent.Method {
return when (this) {
RumResourceMethod.GET -> ResourceEvent.Method.GET
Expand All @@ -45,6 +47,8 @@ internal fun RumResourceMethod.toErrorMethod(): ErrorEvent.Method {
}
}

// endregion

internal fun String.toOperationType(internalLogger: InternalLogger): ResourceEvent.OperationType? {
return try {
ResourceEvent.OperationType.valueOf(this.uppercase(Locale.US))
Expand Down Expand Up @@ -480,3 +484,72 @@ internal const val UNKNOWN_SOURCE_WARNING_MESSAGE_FORMAT = "You are using an unk
"source %s for your events"

// endregion

// region SessionPrecondition conversion

internal fun RumSessionScope.StartReason.toViewSessionPrecondition(): ViewEvent.SessionPrecondition {
return when (this) {
RumSessionScope.StartReason.USER_APP_LAUNCH -> ViewEvent.SessionPrecondition.USER_APP_LAUNCH
RumSessionScope.StartReason.INACTIVITY_TIMEOUT -> ViewEvent.SessionPrecondition.INACTIVITY_TIMEOUT
RumSessionScope.StartReason.MAX_DURATION -> ViewEvent.SessionPrecondition.MAX_DURATION
RumSessionScope.StartReason.EXPLICIT_STOP -> ViewEvent.SessionPrecondition.EXPLICIT_STOP
RumSessionScope.StartReason.BACKGROUND_LAUNCH -> ViewEvent.SessionPrecondition.BACKGROUND_LAUNCH
RumSessionScope.StartReason.PREWARM -> ViewEvent.SessionPrecondition.PREWARM
RumSessionScope.StartReason.FROM_NON_INTERACTIVE_SESSION ->
ViewEvent.SessionPrecondition.FROM_NON_INTERACTIVE_SESSION
}
}

internal fun RumSessionScope.StartReason.toActionSessionPrecondition(): ActionEvent.SessionPrecondition {
return when (this) {
RumSessionScope.StartReason.USER_APP_LAUNCH -> ActionEvent.SessionPrecondition.USER_APP_LAUNCH
RumSessionScope.StartReason.INACTIVITY_TIMEOUT -> ActionEvent.SessionPrecondition.INACTIVITY_TIMEOUT
RumSessionScope.StartReason.MAX_DURATION -> ActionEvent.SessionPrecondition.MAX_DURATION
RumSessionScope.StartReason.EXPLICIT_STOP -> ActionEvent.SessionPrecondition.EXPLICIT_STOP
RumSessionScope.StartReason.BACKGROUND_LAUNCH -> ActionEvent.SessionPrecondition.BACKGROUND_LAUNCH
RumSessionScope.StartReason.PREWARM -> ActionEvent.SessionPrecondition.PREWARM
RumSessionScope.StartReason.FROM_NON_INTERACTIVE_SESSION ->
ActionEvent.SessionPrecondition.FROM_NON_INTERACTIVE_SESSION
}
}

internal fun RumSessionScope.StartReason.toErrorSessionPrecondition(): ErrorEvent.SessionPrecondition {
return when (this) {
RumSessionScope.StartReason.USER_APP_LAUNCH -> ErrorEvent.SessionPrecondition.USER_APP_LAUNCH
RumSessionScope.StartReason.INACTIVITY_TIMEOUT -> ErrorEvent.SessionPrecondition.INACTIVITY_TIMEOUT
RumSessionScope.StartReason.MAX_DURATION -> ErrorEvent.SessionPrecondition.MAX_DURATION
RumSessionScope.StartReason.EXPLICIT_STOP -> ErrorEvent.SessionPrecondition.EXPLICIT_STOP
RumSessionScope.StartReason.BACKGROUND_LAUNCH -> ErrorEvent.SessionPrecondition.BACKGROUND_LAUNCH
RumSessionScope.StartReason.PREWARM -> ErrorEvent.SessionPrecondition.PREWARM
RumSessionScope.StartReason.FROM_NON_INTERACTIVE_SESSION ->
ErrorEvent.SessionPrecondition.FROM_NON_INTERACTIVE_SESSION
}
}

internal fun RumSessionScope.StartReason.toResourceSessionPrecondition(): ResourceEvent.SessionPrecondition {
return when (this) {
RumSessionScope.StartReason.USER_APP_LAUNCH -> ResourceEvent.SessionPrecondition.USER_APP_LAUNCH
RumSessionScope.StartReason.INACTIVITY_TIMEOUT -> ResourceEvent.SessionPrecondition.INACTIVITY_TIMEOUT
RumSessionScope.StartReason.MAX_DURATION -> ResourceEvent.SessionPrecondition.MAX_DURATION
RumSessionScope.StartReason.EXPLICIT_STOP -> ResourceEvent.SessionPrecondition.EXPLICIT_STOP
RumSessionScope.StartReason.BACKGROUND_LAUNCH -> ResourceEvent.SessionPrecondition.BACKGROUND_LAUNCH
RumSessionScope.StartReason.PREWARM -> ResourceEvent.SessionPrecondition.PREWARM
RumSessionScope.StartReason.FROM_NON_INTERACTIVE_SESSION ->
ResourceEvent.SessionPrecondition.FROM_NON_INTERACTIVE_SESSION
}
}

internal fun RumSessionScope.StartReason.toLongTaskSessionPrecondition(): LongTaskEvent.SessionPrecondition {
return when (this) {
RumSessionScope.StartReason.USER_APP_LAUNCH -> LongTaskEvent.SessionPrecondition.USER_APP_LAUNCH
RumSessionScope.StartReason.INACTIVITY_TIMEOUT -> LongTaskEvent.SessionPrecondition.INACTIVITY_TIMEOUT
RumSessionScope.StartReason.MAX_DURATION -> LongTaskEvent.SessionPrecondition.MAX_DURATION
RumSessionScope.StartReason.EXPLICIT_STOP -> LongTaskEvent.SessionPrecondition.EXPLICIT_STOP
RumSessionScope.StartReason.BACKGROUND_LAUNCH -> LongTaskEvent.SessionPrecondition.BACKGROUND_LAUNCH
RumSessionScope.StartReason.PREWARM -> LongTaskEvent.SessionPrecondition.PREWARM
RumSessionScope.StartReason.FROM_NON_INTERACTIVE_SESSION ->
LongTaskEvent.SessionPrecondition.FROM_NON_INTERACTIVE_SESSION
}
}

// endregion
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,10 @@ internal class RumResourceScope(
traceId = traceId,
spanId = spanId,
rulePsr = rulePsr,
session = ResourceEvent.DdSession(plan = ResourceEvent.Plan.PLAN_1),
session = ResourceEvent.DdSession(
plan = ResourceEvent.Plan.PLAN_1,
sessionPrecondition = rumContext.sessionStartReason.toResourceSessionPrecondition()
),
configuration = ResourceEvent.Configuration(sessionSampleRate = sampleRate)
),
service = datadogContext.service,
Expand Down Expand Up @@ -407,7 +410,10 @@ internal class RumResourceScope(
),
context = ErrorEvent.Context(additionalProperties = attributes),
dd = ErrorEvent.Dd(
session = ErrorEvent.DdSession(plan = ErrorEvent.Plan.PLAN_1),
session = ErrorEvent.DdSession(
plan = ErrorEvent.Plan.PLAN_1,
sessionPrecondition = rumContext.sessionStartReason.toErrorSessionPrecondition()
),
configuration = ErrorEvent.Configuration(sessionSampleRate = sampleRate)
),
service = datadogContext.service,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ internal class RumSessionScope(

internal var sessionId = RumContext.NULL_UUID
internal var sessionState: State = State.NOT_TRACKED
private var startReason: StartReason = StartReason.USER_APP_LAUNCH
internal var isActive: Boolean = true
private val sessionStartNs = AtomicLong(System.nanoTime())

private val lastUserInteractionNs = AtomicLong(0L)

private val random = SecureRandom()
Expand Down Expand Up @@ -82,6 +84,23 @@ internal class RumSessionScope(
}
}

enum class StartReason(val asString: String) {
USER_APP_LAUNCH("user_app_launch"),
INACTIVITY_TIMEOUT("inactivity_timeout"),
MAX_DURATION("max_duration"),
BACKGROUND_LAUNCH("background_launch"),
PREWARM("prewarm"),
FROM_NON_INTERACTIVE_SESSION("from_non_interactive_session"),
EXPLICIT_STOP("explicit_stop")
;

companion object {
fun fromString(string: String?): StartReason? {
return values().firstOrNull { it.asString == string }
}
}
}

// region RumScope

@WorkerThread
Expand All @@ -90,7 +109,7 @@ internal class RumSessionScope(
writer: DataWriter<Any>
): RumScope? {
if (event is RumRawEvent.ResetSession) {
renewSession(System.nanoTime())
renewSession(System.nanoTime(), StartReason.EXPLICIT_STOP)
} else if (event is RumRawEvent.StopSession) {
stopSession()
}
Expand All @@ -113,6 +132,7 @@ internal class RumSessionScope(
return parentContext.copy(
sessionId = sessionId,
sessionState = sessionState,
sessionStartReason = startReason,
isSessionActive = isActive
)
}
Expand Down Expand Up @@ -149,25 +169,33 @@ internal class RumSessionScope(

if (isInteraction || isApplicationStartEvent) {
if (isNewSession || isExpired || isTimedOut) {
renewSession(nanoTime)
val reason = if (isNewSession) {
StartReason.USER_APP_LAUNCH
} else if (isExpired) {
StartReason.INACTIVITY_TIMEOUT
} else {
StartReason.MAX_DURATION
}
renewSession(nanoTime, reason)
}
lastUserInteractionNs.set(nanoTime)
} else if (isExpired) {
if (backgroundTrackingEnabled && isBackgroundEvent) {
renewSession(nanoTime)
renewSession(nanoTime, StartReason.INACTIVITY_TIMEOUT)
lastUserInteractionNs.set(nanoTime)
} else {
sessionState = State.EXPIRED
}
} else if (isTimedOut) {
renewSession(nanoTime)
renewSession(nanoTime, StartReason.MAX_DURATION)
}

updateSessionStateForSessionReplay(sessionState, sessionId)
}

private fun renewSession(nanoTime: Long) {
private fun renewSession(nanoTime: Long, reason: StartReason) {
val keepSession = random.nextFloat() < sampleRate.percent()
startReason = reason
sessionState = if (keepSession) State.TRACKED else State.NOT_TRACKED
sessionId = UUID.randomUUID().toString()
sessionStartNs.set(nanoTime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,8 @@ internal open class RumViewScope(
),
synthetics = syntheticsAttribute,
source = ErrorEvent.ErrorEventSource.tryFromSource(
datadogContext.source,
sdkCore.internalLogger
source = datadogContext.source,
internalLogger = sdkCore.internalLogger
),
os = ErrorEvent.Os(
name = datadogContext.deviceInfo.osName,
Expand All @@ -450,7 +450,10 @@ internal open class RumViewScope(
),
context = ErrorEvent.Context(additionalProperties = updatedAttributes),
dd = ErrorEvent.Dd(
session = ErrorEvent.DdSession(plan = ErrorEvent.Plan.PLAN_1),
session = ErrorEvent.DdSession(
plan = ErrorEvent.Plan.PLAN_1,
sessionPrecondition = rumContext.sessionStartReason.toErrorSessionPrecondition()
),
configuration = ErrorEvent.Configuration(sessionSampleRate = sampleRate)
),
service = datadogContext.service,
Expand Down Expand Up @@ -800,7 +803,10 @@ internal open class RumViewScope(
context = ViewEvent.Context(additionalProperties = attributes),
dd = ViewEvent.Dd(
documentVersion = eventVersion,
session = ViewEvent.DdSession(plan = ViewEvent.Plan.PLAN_1),
session = ViewEvent.DdSession(
plan = ViewEvent.Plan.PLAN_1,
sessionPrecondition = rumContext.sessionStartReason.toViewSessionPrecondition()
),
replayStats = replayStats,
configuration = ViewEvent.Configuration(sessionSampleRate = sampleRate)
),
Expand Down Expand Up @@ -935,7 +941,10 @@ internal open class RumViewScope(
additionalProperties = globalAttributes
),
dd = ActionEvent.Dd(
session = ActionEvent.DdSession(ActionEvent.Plan.PLAN_1),
session = ActionEvent.DdSession(
plan = ActionEvent.Plan.PLAN_1,
sessionPrecondition = rumContext.sessionStartReason.toActionSessionPrecondition()
),
configuration = ActionEvent.Configuration(sessionSampleRate = sampleRate)
),
connectivity = datadogContext.networkInfo.toActionConnectivity(),
Expand Down Expand Up @@ -1032,7 +1041,10 @@ internal open class RumViewScope(
),
context = LongTaskEvent.Context(additionalProperties = updatedAttributes),
dd = LongTaskEvent.Dd(
session = LongTaskEvent.DdSession(LongTaskEvent.Plan.PLAN_1),
session = LongTaskEvent.DdSession(
plan = LongTaskEvent.Plan.PLAN_1,
sessionPrecondition = rumContext.sessionStartReason.toLongTaskSessionPrecondition()
),
configuration = LongTaskEvent.Configuration(sessionSampleRate = sampleRate)
),
service = datadogContext.service,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import com.datadog.android.api.context.NetworkInfo
import com.datadog.android.api.context.UserInfo
import com.datadog.android.rum.RumActionType
import com.datadog.android.rum.internal.domain.RumContext
import com.datadog.android.rum.internal.domain.scope.RumSessionScope
import com.datadog.android.rum.internal.domain.scope.isConnected
import com.datadog.android.rum.internal.domain.scope.toActionSessionPrecondition
import com.datadog.android.rum.internal.domain.scope.toSchemaType
import com.datadog.android.rum.model.ActionEvent
import org.assertj.core.api.AbstractObjectAssert
Expand Down Expand Up @@ -332,6 +334,16 @@ internal class ActionEventAssert(actual: ActionEvent) :
return this
}

fun hasStartReason(reason: RumSessionScope.StartReason): ActionEventAssert {
assertThat(actual.dd.session?.sessionPrecondition)
.overridingErrorMessage(
"Expected event to have a session sessionPrecondition of ${reason.name} " +
"but was ${actual.dd.session?.sessionPrecondition}"
)
.isEqualTo(reason.toActionSessionPrecondition())
return this
}

fun hasSource(source: ActionEvent.ActionEventSource?): ActionEventAssert {
assertThat(actual.source)
.overridingErrorMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import com.datadog.android.api.context.UserInfo
import com.datadog.android.rum.RumErrorSource
import com.datadog.android.rum.RumResourceMethod
import com.datadog.android.rum.internal.domain.RumContext
import com.datadog.android.rum.internal.domain.scope.RumSessionScope
import com.datadog.android.rum.internal.domain.scope.isConnected
import com.datadog.android.rum.internal.domain.scope.toErrorMethod
import com.datadog.android.rum.internal.domain.scope.toErrorSessionPrecondition
import com.datadog.android.rum.internal.domain.scope.toSchemaSource
import com.datadog.android.rum.model.ErrorEvent
import org.assertj.core.api.AbstractObjectAssert
Expand Down Expand Up @@ -411,6 +413,16 @@ internal class ErrorEventAssert(actual: ErrorEvent) :
return this
}

fun hasStartReason(reason: RumSessionScope.StartReason): ErrorEventAssert {
assertThat(actual.dd.session?.sessionPrecondition)
.overridingErrorMessage(
"Expected event to have a session sessionPrecondition of ${reason.name} " +
"but was ${actual.dd.session?.sessionPrecondition}"
)
.isEqualTo(reason.toErrorSessionPrecondition())
return this
}

fun hasSource(source: ErrorEvent.ErrorEventSource?): ErrorEventAssert {
assertThat(actual.source)
.overridingErrorMessage(
Expand Down
Loading

0 comments on commit 2f894e9

Please sign in to comment.