Skip to content

Commit 3b7352f

Browse files
authored
Use new API in T+ to obtain process creation request time (#2313)
## Goal Create version-aware wrapper around the Android API to get process creation time. This simplifies the app startup instrumentation, and in addition allows us to use a newer API (T+) to obtain more accurate time for when the process zygote is specialized into the app process. The previous one uses the time the process is created, which could be done well before hand to reduce the latency when the app process is created. ## Testing Unfortunately, there's no way to fake the process start times using Roboletric, so while everything else is unit tested, we can't really differentiate between the two in a test. We just have to trust that API works as expected...
2 parents 3a2b1a9 + 69d4c65 commit 3b7352f

7 files changed

Lines changed: 101 additions & 14 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.embrace.android.embracesdk.internal.process
2+
3+
/**
4+
* Runtime-agnostic interface for getting information from the Android Process API
5+
*/
6+
interface ProcessInfo {
7+
/**
8+
* Return the best-available estimated time for the when the app process was requested to fork
9+
*/
10+
fun startRequestedTimeMs(): Long?
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.embrace.android.embracesdk.internal.process
2+
3+
import android.os.Build.VERSION_CODES
4+
import android.os.Process
5+
import io.embrace.android.embracesdk.internal.utils.VersionChecker
6+
7+
class ProcessInfoImpl(
8+
private val deviceStartTimeMs: Long,
9+
private val versionChecker: VersionChecker,
10+
) : ProcessInfo {
11+
override fun startRequestedTimeMs(): Long? {
12+
return if (versionChecker.isAtLeast(VERSION_CODES.TIRAMISU)) {
13+
deviceStartTimeMs + Process.getStartRequestedElapsedRealtime()
14+
} else if (versionChecker.isAtLeast(VERSION_CODES.N)) {
15+
deviceStartTimeMs + Process.getStartElapsedRealtime()
16+
} else {
17+
null
18+
}
19+
}
20+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.embrace.android.embracesdk.internal.process
2+
3+
import android.os.Build.VERSION_CODES
4+
import android.os.Process
5+
import androidx.test.ext.junit.runners.AndroidJUnit4
6+
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
7+
import org.junit.Assert.assertEquals
8+
import org.junit.Assert.assertNull
9+
import org.junit.Before
10+
import org.junit.Test
11+
import org.junit.runner.RunWith
12+
import org.robolectric.annotation.Config
13+
14+
@RunWith(AndroidJUnit4::class)
15+
internal class ProcessInfoImplTest {
16+
17+
private val fakeDeviceStartTime = 100_000L
18+
private lateinit var processInfo: ProcessInfo
19+
20+
@Before
21+
fun setUp() {
22+
processInfo = ProcessInfoImpl(fakeDeviceStartTime, BuildVersionChecker)
23+
}
24+
25+
@Config(sdk = [VERSION_CODES.TIRAMISU])
26+
@Test
27+
fun `verify start time in T`() {
28+
val startRequestElapsedTime = Process.getStartRequestedElapsedRealtime()
29+
val startRequestedEpochTime = checkNotNull(processInfo.startRequestedTimeMs())
30+
assertEquals(startRequestElapsedTime, startRequestedEpochTime - fakeDeviceStartTime)
31+
}
32+
33+
@Config(sdk = [VERSION_CODES.N])
34+
@Test
35+
fun `verify start time in N`() {
36+
val startElapsedTime = Process.getStartElapsedRealtime()
37+
val startRequestedEpochTime = checkNotNull(processInfo.startRequestedTimeMs())
38+
assertEquals(startElapsedTime, startRequestedEpochTime - fakeDeviceStartTime)
39+
}
40+
41+
@Config(sdk = [VERSION_CODES.LOLLIPOP])
42+
@Test
43+
fun `verify start time in L`() {
44+
assertNull(processInfo.startRequestedTimeMs())
45+
}
46+
}

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package io.embrace.android.embracesdk.internal.capture.startup
22

33
import android.os.Build.VERSION_CODES
4-
import android.os.Process
54
import io.embrace.android.embracesdk.internal.clock.Clock
65
import io.embrace.android.embracesdk.internal.logging.EmbLogger
76
import io.embrace.android.embracesdk.internal.logging.InternalErrorType
87
import io.embrace.android.embracesdk.internal.otel.attrs.embStartupActivityName
98
import io.embrace.android.embracesdk.internal.otel.spans.EmbraceSdkSpan
109
import io.embrace.android.embracesdk.internal.otel.spans.SpanService
10+
import io.embrace.android.embracesdk.internal.process.ProcessInfo
1111
import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessStateListener
1212
import io.embrace.android.embracesdk.internal.ui.hasRenderEvent
1313
import io.embrace.android.embracesdk.internal.ui.supportFrameCommitCallback
@@ -49,12 +49,7 @@ internal class AppStartupTraceEmitter(
4949
private val versionChecker: VersionChecker,
5050
private val logger: EmbLogger,
5151
manualEnd: Boolean,
52-
deviceStartTimestampMs: Long,
53-
private val processCreatedMs: Long? = if (versionChecker.isAtLeast(VERSION_CODES.N)) {
54-
deviceStartTimestampMs + Process.getStartElapsedRealtime()
55-
} else {
56-
null
57-
},
52+
processInfo: ProcessInfo,
5853
) : AppStartupDataCollector, ProcessStateListener {
5954
private val additionalTrackedIntervals = ConcurrentLinkedQueue<TrackedInterval>()
6055
private val customAttributes: MutableMap<String, String> = ConcurrentHashMap()
@@ -70,6 +65,8 @@ internal class AppStartupTraceEmitter(
7065
TraceEnd.RESUMED
7166
}
7267

68+
private val processCreatedMs: Long? = processInfo.startRequestedTimeMs()
69+
7370
@Volatile
7471
private var applicationInitStartMs: Long? = null
7572

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImpl.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import io.embrace.android.embracesdk.internal.capture.startup.StartupTracker
1313
import io.embrace.android.embracesdk.internal.capture.webview.EmbraceWebViewService
1414
import io.embrace.android.embracesdk.internal.capture.webview.WebViewService
1515
import io.embrace.android.embracesdk.internal.config.ConfigService
16+
import io.embrace.android.embracesdk.internal.process.ProcessInfoImpl
1617
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
1718
import io.embrace.android.embracesdk.internal.ui.createDrawEventEmitter
1819
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
@@ -59,7 +60,10 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor(
5960
versionChecker = versionChecker,
6061
logger = initModule.logger,
6162
manualEnd = configService.autoDataCaptureBehavior.isEndStartupWithAppReadyEnabled(),
62-
deviceStartTimestampMs = openTelemetryModule.deviceStartTimeMs()
63+
processInfo = ProcessInfoImpl(
64+
deviceStartTimeMs = openTelemetryModule.deviceStartTimeMs(),
65+
versionChecker = versionChecker,
66+
)
6367
)
6468
}
6569

embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitterTest.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import io.embrace.android.embracesdk.arch.assertError
77
import io.embrace.android.embracesdk.fakes.FakeClock
88
import io.embrace.android.embracesdk.fakes.FakeClock.Companion.DEFAULT_FAKE_CURRENT_TIME
99
import io.embrace.android.embracesdk.fakes.FakeEmbLogger
10+
import io.embrace.android.embracesdk.fakes.FakeProcessInfo
1011
import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
1112
import io.embrace.android.embracesdk.internal.capture.activity.hasPrePostEvents
1213
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupTraceEmitter.Companion.ACTIVITY_FIRST_DRAW_SPAN
@@ -608,12 +609,13 @@ internal class AppStartupTraceEmitterTest {
608609
versionChecker = BuildVersionChecker,
609610
logger = logger,
610611
manualEnd = manualEnd,
611-
deviceStartTimestampMs = DEFAULT_FAKE_CURRENT_TIME - 10000000L,
612-
processCreatedMs = if (BuildVersionChecker.isAtLeast(VERSION_CODES.N)) {
613-
DEFAULT_FAKE_CURRENT_TIME
614-
} else {
615-
null
616-
}
612+
processInfo = FakeProcessInfo(
613+
if (BuildVersionChecker.isAtLeast(VERSION_CODES.N)) {
614+
DEFAULT_FAKE_CURRENT_TIME
615+
} else {
616+
null
617+
}
618+
)
617619
)
618620

619621
private fun AppStartupTraceEmitter.simulateAppStartup(
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.embrace.android.embracesdk.fakes
2+
3+
import io.embrace.android.embracesdk.internal.process.ProcessInfo
4+
5+
class FakeProcessInfo(private val fakeStartRequestedTime: Long?) : ProcessInfo {
6+
override fun startRequestedTimeMs(): Long? = fakeStartRequestedTime
7+
}

0 commit comments

Comments
 (0)