Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
updated changelog
added tests
  • Loading branch information
stefanosiano committed Jul 4, 2024
commit a12a15cb7a67490552906193513eaeaf915e7016
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- Check app start spans time and foreground state ([#3550](https://github.com/getsentry/sentry-java/pull/3550))

## 7.11.0

### Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import org.robolectric.shadow.api.Shadow
import org.robolectric.shadows.ShadowActivityManager
import java.util.Date
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
Expand Down Expand Up @@ -941,6 +942,46 @@ class ActivityLifecycleIntegrationTest {
assertEquals(span.startDate.nanoTimestamp(), date.nanoTimestamp())
}

@Test
fun `When firstActivityCreated is true and app started more than 1 minute ago, app start spans are dropped`() {
val sut = fixture.getSut()
fixture.options.tracesSampleRate = 1.0
sut.register(fixture.hub, fixture.options)

val date = SentryNanotimeDate(Date(1), 0)
val duration = TimeUnit.MINUTES.toMillis(1) + 2
val durationNanos = TimeUnit.MILLISECONDS.toNanos(duration)
val stopDate = SentryNanotimeDate(Date(duration), durationNanos)
setAppStartTime(date, stopDate)

val activity = mock<Activity>()
sut.onActivityCreated(activity, null)

val appStartSpan = fixture.transaction.children.firstOrNull {
it.description == "Cold Start"
}
assertNull(appStartSpan)
}

@Test
fun `When firstActivityCreated is true and app started in background, app start spans are dropped`() {
val sut = fixture.getSut()
AppStartMetrics.getInstance().isAppLaunchedInForeground = false
fixture.options.tracesSampleRate = 1.0
sut.register(fixture.hub, fixture.options)

val date = SentryNanotimeDate(Date(1), 0)
setAppStartTime(date)

val activity = mock<Activity>()
sut.onActivityCreated(activity, null)

val appStartSpan = fixture.transaction.children.firstOrNull {
it.description == "Cold Start"
}
assertNull(appStartSpan)
}

@Test
fun `When firstActivityCreated is false, start transaction but not with given appStartTime`() {
val sut = fixture.getSut()
Expand Down Expand Up @@ -1413,18 +1454,19 @@ class ActivityLifecycleIntegrationTest {
shadowOf(Looper.getMainLooper()).idle()
}

private fun setAppStartTime(date: SentryDate = SentryNanotimeDate(Date(1), 0)) {
private fun setAppStartTime(date: SentryDate = SentryNanotimeDate(Date(1), 0), stopDate: SentryDate = SentryNanotimeDate(Date(0), 0)) {
// set by SentryPerformanceProvider so forcing it here
val sdkAppStartTimeSpan = AppStartMetrics.getInstance().sdkInitTimeSpan
val appStartTimeSpan = AppStartMetrics.getInstance().appStartTimeSpan
val millis = DateUtils.nanosToMillis(date.nanoTimestamp().toDouble()).toLong()
val stopMillis = DateUtils.nanosToMillis(stopDate.nanoTimestamp().toDouble()).toLong()

sdkAppStartTimeSpan.setStartedAt(millis)
sdkAppStartTimeSpan.setStartUnixTimeMs(millis)
sdkAppStartTimeSpan.setStoppedAt(0)
sdkAppStartTimeSpan.setStoppedAt(stopMillis)

appStartTimeSpan.setStartedAt(millis)
appStartTimeSpan.setStartUnixTimeMs(millis)
appStartTimeSpan.setStoppedAt(0)
appStartTimeSpan.setStoppedAt(stopMillis)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import io.sentry.android.core.performance.ActivityLifecycleTimeSpan
import io.sentry.android.core.performance.AppStartMetrics
import io.sentry.android.core.performance.AppStartMetrics.AppStartType
import io.sentry.protocol.MeasurementValue
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentrySpan
import io.sentry.protocol.SentryTransaction
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import java.util.concurrent.TimeUnit
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
Expand Down Expand Up @@ -57,6 +59,22 @@ class PerformanceAndroidEventProcessorTest {

private val fixture = Fixture()

private fun createAppStartSpan(traceId: SentryId) = SentrySpan(
0.0,
1.0,
traceId,
SpanId(),
null,
APP_START_COLD,
"App Start",
SpanStatus.OK,
null,
emptyMap(),
emptyMap(),
null,
null
)

@BeforeTest
fun `reset instance`() {
AppStartMetrics.getInstance().clear()
Expand Down Expand Up @@ -234,21 +252,7 @@ class PerformanceAndroidEventProcessorTest {
var tr = SentryTransaction(tracer)

// and it contains an app.start.cold span
val appStartSpan = SentrySpan(
0.0,
1.0,
tr.contexts.trace!!.traceId,
SpanId(),
null,
APP_START_COLD,
"App Start",
SpanStatus.OK,
null,
emptyMap(),
emptyMap(),
null,
null
)
val appStartSpan = createAppStartSpan(tr.contexts.trace!!.traceId)
tr.spans.add(appStartSpan)

// then the app start metrics should be attached
Expand Down Expand Up @@ -286,6 +290,110 @@ class PerformanceAndroidEventProcessorTest {
)
}

@Test
fun `when app launched from background, app start spans are dropped`() {
// given some app start metrics
val appStartMetrics = AppStartMetrics.getInstance()
appStartMetrics.appStartType = AppStartType.COLD
appStartMetrics.appStartTimeSpan.setStartedAt(123)
appStartMetrics.appStartTimeSpan.setStoppedAt(456)

val contentProvider = mock<ContentProvider>()
AppStartMetrics.onContentProviderCreate(contentProvider)
AppStartMetrics.onContentProviderPostCreate(contentProvider)

appStartMetrics.applicationOnCreateTimeSpan.apply {
setStartedAt(10)
setStoppedAt(42)
}

val activityTimeSpan = ActivityLifecycleTimeSpan()
activityTimeSpan.onCreate.description = "MainActivity.onCreate"
activityTimeSpan.onStart.description = "MainActivity.onStart"

activityTimeSpan.onCreate.setStartedAt(200)
activityTimeSpan.onStart.setStartedAt(220)
activityTimeSpan.onStart.setStoppedAt(240)
activityTimeSpan.onCreate.setStoppedAt(260)
appStartMetrics.addActivityLifecycleTimeSpans(activityTimeSpan)

// when an activity transaction is created
val sut = fixture.getSut(enablePerformanceV2 = true)
val context = TransactionContext("Activity", UI_LOAD_OP)
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)

// and it contains an app.start.cold span
val appStartSpan = createAppStartSpan(tr.contexts.trace!!.traceId)
tr.spans.add(appStartSpan)

// but app is launched in background
AppStartMetrics.getInstance().isAppLaunchedInForeground = false

// then the app start metrics are not attached
tr = sut.process(tr, Hint())

assertFalse(
tr.spans.any {
"process.load" == it.op ||
"contentprovider.load" == it.op ||
"application.load" == it.op ||
"activity.load" == it.op
}
)
}

@Test
fun `when app start takes more than 1 minute, app start spans are dropped`() {
// given some app start metrics
val appStartMetrics = AppStartMetrics.getInstance()
appStartMetrics.appStartType = AppStartType.COLD
appStartMetrics.appStartTimeSpan.setStartedAt(123)
// and app start takes more than 1 minute
appStartMetrics.appStartTimeSpan.setStoppedAt(TimeUnit.MINUTES.toMillis(1) + 124)

val contentProvider = mock<ContentProvider>()
AppStartMetrics.onContentProviderCreate(contentProvider)
AppStartMetrics.onContentProviderPostCreate(contentProvider)

appStartMetrics.applicationOnCreateTimeSpan.apply {
setStartedAt(10)
setStoppedAt(42)
}

val activityTimeSpan = ActivityLifecycleTimeSpan()
activityTimeSpan.onCreate.description = "MainActivity.onCreate"
activityTimeSpan.onStart.description = "MainActivity.onStart"

activityTimeSpan.onCreate.setStartedAt(200)
activityTimeSpan.onStart.setStartedAt(220)
activityTimeSpan.onStart.setStoppedAt(240)
activityTimeSpan.onCreate.setStoppedAt(260)
appStartMetrics.addActivityLifecycleTimeSpans(activityTimeSpan)

// when an activity transaction is created
val sut = fixture.getSut(enablePerformanceV2 = true)
val context = TransactionContext("Activity", UI_LOAD_OP)
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)

// and it contains an app.start.cold span
val appStartSpan = createAppStartSpan(tr.contexts.trace!!.traceId)
tr.spans.add(appStartSpan)

// then the app start metrics are not attached
tr = sut.process(tr, Hint())

assertFalse(
tr.spans.any {
"process.load" == it.op ||
"contentprovider.load" == it.op ||
"application.load" == it.op ||
"activity.load" == it.op
}
)
}

@Test
fun `does not add app start metrics to app start txn when it is not a cold start`() {
// given some WARM app start metrics
Expand Down Expand Up @@ -331,21 +439,7 @@ class PerformanceAndroidEventProcessorTest {
val context = TransactionContext("Activity", UI_LOAD_OP)
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)
val appStartSpan = SentrySpan(
0.0,
1.0,
tr.contexts.trace!!.traceId,
SpanId(),
null,
APP_START_COLD,
"App Start",
SpanStatus.OK,
null,
emptyMap(),
emptyMap(),
null,
null
)
val appStartSpan = createAppStartSpan(tr.contexts.trace!!.traceId)
tr.spans.add(appStartSpan)

// then the app start metrics should not be attached
Expand Down Expand Up @@ -382,21 +476,7 @@ class PerformanceAndroidEventProcessorTest {
val context = TransactionContext("Activity", UI_LOAD_OP)
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)
val appStartSpan = SentrySpan(
0.0,
1.0,
tr.contexts.trace!!.traceId,
SpanId(),
null,
APP_START_COLD,
"App Start",
SpanStatus.OK,
null,
emptyMap(),
emptyMap(),
null,
null
)
val appStartSpan = createAppStartSpan(tr.contexts.trace!!.traceId)
tr.spans.add(appStartSpan)

// when the processor attaches the app start spans
Expand Down Expand Up @@ -429,21 +509,7 @@ class PerformanceAndroidEventProcessorTest {
val context = TransactionContext("Activity", UI_LOAD_OP)
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)
val appStartSpan = SentrySpan(
0.0,
1.0,
tr.contexts.trace!!.traceId,
SpanId(),
null,
APP_START_COLD,
"App Start",
SpanStatus.OK,
null,
emptyMap(),
emptyMap(),
null,
null
)
val appStartSpan = createAppStartSpan(tr.contexts.trace!!.traceId)
tr.spans.add(appStartSpan)

// when the processor attaches the app start spans
Expand Down Expand Up @@ -494,21 +560,7 @@ class PerformanceAndroidEventProcessorTest {
val tracer = SentryTracer(context, fixture.hub)
var tr = SentryTransaction(tracer)

val appStartSpan = SentrySpan(
0.0,
1.0,
tr.contexts.trace!!.traceId,
SpanId(),
null,
APP_START_COLD,
"App Start",
SpanStatus.OK,
null,
emptyMap(),
emptyMap(),
null,
null
)
val appStartSpan = createAppStartSpan(tr.contexts.trace!!.traceId)
tr.spans.add(appStartSpan)

// when the processor attaches the app start spans
Expand Down