Skip to content

Commit 5f78f0b

Browse files
committed
chore(android): add session start time attribute to events and spans
Capture session start time in SessionManager and attach it to every event and span via a new SessionAttributeProcessor.
1 parent f00cd1a commit 5f78f0b

File tree

6 files changed

+73
-0
lines changed

6 files changed

+73
-0
lines changed

android/measure/src/main/java/sh/measure/android/MeasureInitializer.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import sh.measure.android.attributes.DeviceAttributeProcessor
1313
import sh.measure.android.attributes.InstallationIdAttributeProcessor
1414
import sh.measure.android.attributes.NetworkStateAttributeProcessor
1515
import sh.measure.android.attributes.PowerStateAttributeProcessor
16+
import sh.measure.android.attributes.SessionAttributeProcessor
1617
import sh.measure.android.attributes.SpanDeviceAttributeProcessor
1718
import sh.measure.android.attributes.UserAttributeProcessor
1819
import sh.measure.android.bugreport.AccelerometerShakeDetector
@@ -216,13 +217,17 @@ internal class MeasureInitializerImpl(
216217
private val powerStateAttributeProcessor: PowerStateAttributeProcessor = PowerStateAttributeProcessor(
217218
powerStateProvider = powerStateProvider,
218219
),
220+
private val sessionAttributeProcessor: SessionAttributeProcessor = SessionAttributeProcessor(
221+
sessionManager = sessionManager,
222+
),
219223
private val attributeProcessors: List<AttributeProcessor> = listOf(
220224
userAttributeProcessor,
221225
deviceAttributeProcessor,
222226
appAttributeProcessor,
223227
installationIdAttributeProcessor,
224228
networkStateAttributeProcessor,
225229
powerStateAttributeProcessor,
230+
sessionAttributeProcessor,
226231
),
227232
private val signalStore: SignalStore = SignalStoreImpl(
228233
logger = logger,
@@ -348,6 +353,7 @@ internal class MeasureInitializerImpl(
348353
installationIdAttributeProcessor,
349354
networkStateAttributeProcessor,
350355
powerStateAttributeProcessor,
356+
sessionAttributeProcessor,
351357
),
352358
override val spanProcessor: SpanProcessor = MsrSpanProcessor(
353359
logger,

android/measure/src/main/java/sh/measure/android/SessionManager.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ internal interface SessionManager {
3434
@Throws(IllegalArgumentException::class)
3535
fun getSessionId(): String
3636

37+
/**
38+
* Returns the start time of the current session.
39+
*
40+
* @throws IllegalArgumentException if the SDK has not been initialized and the session
41+
* has not been created.
42+
*/
43+
@Throws(IllegalArgumentException::class)
44+
fun getSessionStartTime(): Long
45+
3746
/**
3847
* Called when app comes to foreground
3948
*/
@@ -68,6 +77,7 @@ internal class SessionManagerImpl(
6877
private val sampler: Sampler,
6978
) : SessionManager {
7079
private var sessionId: String? = null
80+
private var sessionStartTime: Long? = null
7181

7282
@VisibleForTesting
7383
internal var appBackgroundTime: Long = 0
@@ -108,6 +118,14 @@ internal class SessionManagerImpl(
108118
return sessionId
109119
}
110120

121+
override fun getSessionStartTime(): Long {
122+
val startTime = this.sessionStartTime
123+
requireNotNull(startTime) {
124+
"SDK must be initialized before accessing session start time"
125+
}
126+
return startTime
127+
}
128+
111129
override fun onConfigLoaded() {
112130
val currentSessionId = sessionId
113131
if (currentSessionId == null) {
@@ -136,6 +154,7 @@ internal class SessionManagerImpl(
136154
private fun createNewSession(): String {
137155
val id = idProvider.uuid()
138156
this.sessionId = id
157+
this.sessionStartTime = timeProvider.now()
139158
storeSession(id)
140159
return id
141160
}

android/measure/src/main/java/sh/measure/android/attributes/Attribute.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ internal object Attribute {
2828
const val USER_ID_KEY = "user_id"
2929
const val DEVICE_LOW_POWER_ENABLED = "device_low_power_mode"
3030
const val DEVICE_THERMAL_THROTTLING_ENABLED = "device_thermal_throttling_enabled"
31+
const val SESSION_START_TIME_KEY = "session_start_time"
3132
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package sh.measure.android.attributes
2+
3+
import sh.measure.android.SessionManager
4+
5+
/**
6+
* Generates the session start time attribute. This attribute changes when a new session is created,
7+
* so it is computed every time [appendAttributes] is called.
8+
*/
9+
internal class SessionAttributeProcessor(
10+
private val sessionManager: SessionManager,
11+
) : AttributeProcessor {
12+
override fun appendAttributes(attributes: MutableMap<String, Any?>) {
13+
attributes.put(Attribute.SESSION_START_TIME_KEY, sessionManager.getSessionStartTime())
14+
}
15+
}

android/measure/src/test/java/sh/measure/android/SessionManagerTest.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,35 @@ class SessionManagerTest {
5757
}
5858
}
5959

60+
@Test
61+
fun `getSessionStartTime throws when not initialized`() {
62+
assertThrows(IllegalArgumentException::class.java) {
63+
sessionManager.getSessionStartTime()
64+
}
65+
}
66+
67+
@Test
68+
fun `getSessionStartTime returns correct time after init`() {
69+
val expectedTime = timeProvider.now()
70+
sessionManager.init()
71+
assertEquals(expectedTime, sessionManager.getSessionStartTime())
72+
}
73+
74+
@Test
75+
fun `getSessionStartTime updates when new session is created on foreground`() {
76+
sessionManager.init()
77+
val initialStartTime = sessionManager.getSessionStartTime()
78+
79+
sessionManager.onAppBackground()
80+
testClock.advance(Duration.ofMillis(configProvider.sessionBackgroundTimeoutThresholdMs + 1))
81+
idProvider.id = "next-uuid"
82+
83+
sessionManager.onAppForeground()
84+
85+
val newStartTime = sessionManager.getSessionStartTime()
86+
assert(newStartTime > initialStartTime)
87+
}
88+
6089
@Test
6190
fun `init creates and returns session id`() {
6291
val s1 = sessionManager.init()

android/measure/src/test/java/sh/measure/android/fakes/FakeSessionManager.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import sh.measure.android.SessionManager
44

55
internal class FakeSessionManager : SessionManager {
66
var id = "fake-session-id"
7+
var startTime: Long = 0L
78

89
override fun init(): String = id
910

1011
override fun getSessionId(): String = id
1112

13+
override fun getSessionStartTime(): Long = startTime
14+
1215
override fun onAppForeground() {
1316
// no-op
1417
}

0 commit comments

Comments
 (0)