Skip to content

Commit d1262c2

Browse files
fix(android): Align foreground app start measurements
Use the foreground app start fallback for foreground standalone app start transactions so measurements match the transaction timestamp. Keep the headless-only span source limited to true headless starts. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 38ac42c commit d1262c2

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public final class ActivityLifecycleIntegration
6464
static final String TTFD_OP = "ui.load.full_display";
6565
static final long TTFD_TIMEOUT_MILLIS = 25000;
6666
private static final String TRACE_ORIGIN = "auto.ui.activity";
67-
private static final String APP_START_SCREEN_DATA = "app.vitals.start.screen";
67+
static final String APP_START_SCREEN_DATA = "app.vitals.start.screen";
6868

6969
private final @NotNull Application application;
7070
private final @NotNull BuildInfoProvider buildInfoProvider;

sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry.android.core;
22

33
import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_COLD;
4+
import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_SCREEN_DATA;
45
import static io.sentry.android.core.ActivityLifecycleIntegration.APP_START_WARM;
56
import static io.sentry.android.core.ActivityLifecycleIntegration.STANDALONE_APP_START_OP;
67
import static io.sentry.android.core.ActivityLifecycleIntegration.UI_LOAD_OP;
@@ -85,15 +86,19 @@ public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
8586
// the app start measurement is only sent once and only if the transaction has
8687
// the app.start span, which is automatically created by the SDK.
8788
if (hasAppStartSpan(transaction)) {
88-
// For headless starts, appLaunchedInForeground is false, so standalone app start
89-
// transactions bypass only the foreground check, not the duplicate-send guard.
89+
// For headless starts, appLaunchedInForeground is false, so only headless standalone app
90+
// start transactions bypass the foreground check, not the duplicate-send guard.
9091
final @Nullable SpanContext traceContext = transaction.getContexts().getTrace();
9192
final boolean isStandaloneAppStartTxn =
9293
traceContext != null && STANDALONE_APP_START_OP.equals(traceContext.getOperation());
94+
final boolean isHeadlessStandaloneAppStartTxn =
95+
traceContext != null
96+
&& isStandaloneAppStartTxn
97+
&& !traceContext.getData().containsKey(APP_START_SCREEN_DATA);
9398

94-
if (appStartMetrics.shouldSendStartMeasurements(isStandaloneAppStartTxn)) {
99+
if (appStartMetrics.shouldSendStartMeasurements(isHeadlessStandaloneAppStartTxn)) {
95100
final @NotNull TimeSpan appStartTimeSpan =
96-
isStandaloneAppStartTxn
101+
isHeadlessStandaloneAppStartTxn
97102
? appStartMetrics.getAppStartTimeSpanForHeadless()
98103
: appStartMetrics.getAppStartTimeSpanWithFallback(options);
99104
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();

sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import io.sentry.SpanStatus
1313
import io.sentry.TracesSamplingDecision
1414
import io.sentry.TransactionContext
1515
import io.sentry.android.core.ActivityLifecycleIntegration.APP_START_COLD
16+
import io.sentry.android.core.ActivityLifecycleIntegration.APP_START_SCREEN_DATA
1617
import io.sentry.android.core.ActivityLifecycleIntegration.APP_START_WARM
1718
import io.sentry.android.core.ActivityLifecycleIntegration.STANDALONE_APP_START_OP
1819
import io.sentry.android.core.ActivityLifecycleIntegration.UI_LOAD_OP
@@ -169,6 +170,30 @@ class PerformanceAndroidEventProcessorTest {
169170
}
170171
}
171172

173+
@Test
174+
fun `foreground standalone app start measurement uses foreground fallback time span`() {
175+
val sut = fixture.getSut(enablePerformanceV2 = false)
176+
AppStartMetrics.getInstance().apply {
177+
appStartType = AppStartType.COLD
178+
isAppLaunchedInForeground = true
179+
appStartTimeSpan.apply {
180+
setStartedAt(1)
181+
setStoppedAt(101)
182+
}
183+
sdkInitTimeSpan.apply {
184+
setStartedAt(10)
185+
setStoppedAt(30)
186+
}
187+
}
188+
189+
var tr = getTransaction(AppStartType.COLD)
190+
tr.contexts.trace!!.setData(APP_START_SCREEN_DATA, "MainActivity")
191+
192+
tr = sut.process(tr, Hint())
193+
194+
assertEquals(20f, tr.measurements[MeasurementValue.KEY_APP_START_COLD]?.value)
195+
}
196+
172197
@Test
173198
fun `add cold start measurement for performance-v2`() {
174199
val sut = fixture.getSut(enablePerformanceV2 = true)

0 commit comments

Comments
 (0)