Skip to content

Commit c4eeb3c

Browse files
authored
Continuous Profiling - stop when app goes in background (#4311)
* Replaced synchronized blocks with AutoClosableReentrantLock in AndroidContinuousProfiler * Added "delayed" stop of profiler, which stops the profiler after the current chunk is finished * Added default span data (profiler id, thread name and thread id) to transaction root span * App going in the background now stops the continuous profiler * Added isTerminating param to AndroidContinuousProfiler.close()
1 parent f6625b0 commit c4eeb3c

File tree

17 files changed

+53
-21
lines changed

17 files changed

+53
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Features
66

7+
- Continuous Profiling - stop when app goes in background ([#4311](https://github.com/getsentry/sentry-java/pull/4311))
78
- Continuous Profiling - Add delayed stop ([#4293](https://github.com/getsentry/sentry-java/pull/4293))
89
- Continuous Profiling - Out of Experimental ([#4310](https://github.com/getsentry/sentry-java/pull/4310))
910

sentry-android-core/api/sentry-android-core.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
4242

4343
public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler, io/sentry/transport/RateLimiter$IRateLimitObserver {
4444
public fun <init> (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/ISentryExecutorService;)V
45-
public fun close ()V
45+
public fun close (Z)V
4646
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
4747
public fun getRootSpanCounter ()I
4848
public fun isRunning ()Z

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,12 +322,15 @@ public void reevaluateSampling() {
322322
shouldSample = true;
323323
}
324324

325-
public void close() {
325+
@Override
326+
public void close(final boolean isTerminating) {
326327
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
327328
rootSpanCounter = 0;
328329
shouldStop = true;
329-
stop(false);
330-
isClosed.set(true);
330+
if (isTerminating) {
331+
stop(false);
332+
isClosed.set(true);
333+
}
331334
}
332335
}
333336

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ private static void setupProfiler(
268268
// This is a safeguard, but it should never happen, as the app start profiler should be the
269269
// continuous one.
270270
if (appStartContinuousProfiler != null) {
271-
appStartContinuousProfiler.close();
271+
appStartContinuousProfiler.close(true);
272272
}
273273
if (appStartTransactionProfiler != null) {
274274
options.setTransactionProfiler(appStartTransactionProfiler);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ public void run() {
122122
scopes.endSession();
123123
}
124124
scopes.getOptions().getReplayController().stop();
125+
scopes.getOptions().getContinuousProfiler().close(false);
125126
}
126127
};
127128

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void shutdown() {
9595
final @Nullable IContinuousProfiler appStartContinuousProfiler =
9696
AppStartMetrics.getInstance().getAppStartContinuousProfiler();
9797
if (appStartContinuousProfiler != null) {
98-
appStartContinuousProfiler.close();
98+
appStartContinuousProfiler.close(true);
9999
}
100100
}
101101
}

sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ public void clear() {
225225
}
226226
appStartProfiler = null;
227227
if (appStartContinuousProfiler != null) {
228-
appStartContinuousProfiler.close();
228+
appStartContinuousProfiler.close(true);
229229
}
230230
appStartContinuousProfiler = null;
231231
appStartSamplingDecision = null;
@@ -333,7 +333,7 @@ private void checkCreateTimeOnMain() {
333333
appStartProfiler = null;
334334
}
335335
if (appStartContinuousProfiler != null && appStartContinuousProfiler.isRunning()) {
336-
appStartContinuousProfiler.close();
336+
appStartContinuousProfiler.close(true);
337337
appStartContinuousProfiler = null;
338338
}
339339
}

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ class AndroidContinuousProfilerTest {
389389
profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler)
390390
assertTrue(profiler.isRunning)
391391

392-
profiler.close()
392+
profiler.close(true)
393393
assertFalse(profiler.isRunning)
394394

395395
// The timeout scheduled job should be cleared
@@ -470,14 +470,31 @@ class AndroidContinuousProfilerTest {
470470
verify(fixture.scopes).captureProfileChunk(any())
471471
}
472472

473+
@Test
474+
fun `close without terminating stops all profiles after chunk is finished`() {
475+
val profiler = fixture.getSut()
476+
profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler)
477+
profiler.startProfiler(ProfileLifecycle.TRACE, fixture.mockTracesSampler)
478+
assertTrue(profiler.isRunning)
479+
// We are scheduling the profiler to stop at the end of the chunk, so it should still be running
480+
profiler.close(false)
481+
assertTrue(profiler.isRunning)
482+
// However, close() already resets the rootSpanCounter
483+
assertEquals(0, profiler.rootSpanCounter)
484+
485+
// We run the executor service to trigger the chunk finish, and the profiler shouldn't restart
486+
fixture.executor.runAll()
487+
assertFalse(profiler.isRunning)
488+
}
489+
473490
@Test
474491
fun `profiler does not send chunks after close`() {
475492
val profiler = fixture.getSut()
476493
profiler.startProfiler(ProfileLifecycle.MANUAL, fixture.mockTracesSampler)
477494
assertTrue(profiler.isRunning)
478495

479496
// We close the profiler, which should prevent sending additional chunks
480-
profiler.close()
497+
profiler.close(true)
481498

482499
// The executor used to send the chunk doesn't do anything
483500
fixture.executor.runAll()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ class AndroidOptionsInitializerTest {
446446
assertEquals(fixture.sentryOptions.continuousProfiler, NoOpContinuousProfiler.getInstance())
447447

448448
// app start profiler is closed, because it will never be used
449-
verify(appStartContinuousProfiler).close()
449+
verify(appStartContinuousProfiler).close(eq(true))
450450

451451
// AppStartMetrics should be cleared
452452
assertNull(AppStartMetrics.getInstance().appStartProfiler)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.sentry.android.core
33
import androidx.lifecycle.LifecycleOwner
44
import io.sentry.Breadcrumb
55
import io.sentry.DateUtils
6+
import io.sentry.IContinuousProfiler
67
import io.sentry.IScope
78
import io.sentry.IScopes
89
import io.sentry.ReplayController
@@ -15,6 +16,7 @@ import io.sentry.transport.ICurrentDateProvider
1516
import org.mockito.ArgumentCaptor
1617
import org.mockito.kotlin.any
1718
import org.mockito.kotlin.check
19+
import org.mockito.kotlin.eq
1820
import org.mockito.kotlin.mock
1921
import org.mockito.kotlin.never
2022
import org.mockito.kotlin.timeout
@@ -38,6 +40,7 @@ class LifecycleWatcherTest {
3840
val dateProvider = mock<ICurrentDateProvider>()
3941
val options = SentryOptions()
4042
val replayController = mock<ReplayController>()
43+
val continuousProfiler = mock<IContinuousProfiler>()
4144

4245
fun getSUT(
4346
sessionIntervalMillis: Long = 0L,
@@ -52,6 +55,7 @@ class LifecycleWatcherTest {
5255
argumentCaptor.value.run(scope)
5356
}
5457
options.setReplayController(replayController)
58+
options.setContinuousProfiler(continuousProfiler)
5559
whenever(scopes.options).thenReturn(options)
5660

5761
return LifecycleWatcher(
@@ -106,6 +110,7 @@ class LifecycleWatcherTest {
106110
watcher.onStop(fixture.ownerMock)
107111
verify(fixture.scopes, timeout(10000)).endSession()
108112
verify(fixture.replayController, timeout(10000)).stop()
113+
verify(fixture.continuousProfiler, timeout(10000)).close(eq(false))
109114
}
110115

111116
@Test

0 commit comments

Comments
 (0)