Skip to content

Commit c0a7fd1

Browse files
authored
Handle App Start Continuous Profiling v8 (p4) (#3730)
* create app start continuous profiler instead of transaction profiler, based on config * updated SentryAppStartProfilingOptions with isContinuousProfilingEnabled flag * updated SentryOptions with isContinuousProfilingEnabled() method * cut profiler setup out in a specific function to improve readability of AndroidOptionsInitializer Add new APIs for Continuous Profiling v8 (p5) (#3844) * AndroidContinuousProfiler now retrieve the scopes on start() * removed profilesSampleRate from sample app to enable continuous profiling * added Sentry.startProfiler and Sentry.stopProfiler APIs
1 parent 9233f8a commit c0a7fd1

31 files changed

+618
-91
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IConti
4141
public fun close ()V
4242
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
4343
public fun isRunning ()Z
44-
public fun setScopes (Lio/sentry/IScopes;)V
4544
public fun start ()V
4645
public fun stop ()V
4746
}
@@ -448,6 +447,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
448447
public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V
449448
public fun clear ()V
450449
public fun getActivityLifecycleTimeSpans ()Ljava/util/List;
450+
public fun getAppStartContinuousProfiler ()Lio/sentry/IContinuousProfiler;
451451
public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler;
452452
public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision;
453453
public fun getAppStartTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
@@ -466,6 +466,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
466466
public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V
467467
public fun registerApplicationForegroundCheck (Landroid/app/Application;)V
468468
public fun setAppLaunchedInForeground (Z)V
469+
public fun setAppStartContinuousProfiler (Lio/sentry/IContinuousProfiler;)V
469470
public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V
470471
public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
471472
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import io.sentry.ILogger;
1010
import io.sentry.IScopes;
1111
import io.sentry.ISentryExecutorService;
12+
import io.sentry.NoOpScopes;
1213
import io.sentry.PerformanceCollectionData;
1314
import io.sentry.ProfileChunk;
15+
import io.sentry.Sentry;
1416
import io.sentry.SentryLevel;
1517
import io.sentry.SentryOptions;
1618
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
@@ -88,12 +90,14 @@ private void init() {
8890
logger);
8991
}
9092

91-
public synchronized void setScopes(final @NotNull IScopes scopes) {
92-
this.scopes = scopes;
93-
this.performanceCollector = scopes.getOptions().getCompositePerformanceCollector();
94-
}
95-
9693
public synchronized void start() {
94+
if ((scopes == null || scopes != NoOpScopes.getInstance())
95+
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
96+
this.scopes = Sentry.getCurrentScopes();
97+
this.performanceCollector =
98+
Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector();
99+
}
100+
97101
// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
98102
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
99103
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;

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

+69-35
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.content.pm.PackageInfo;
88
import io.sentry.DeduplicateMultithreadedEventProcessor;
99
import io.sentry.DefaultCompositePerformanceCollector;
10+
import io.sentry.IContinuousProfiler;
1011
import io.sentry.ILogger;
1112
import io.sentry.ISentryLifecycleToken;
1213
import io.sentry.ITransactionProfiler;
@@ -158,43 +159,26 @@ static void initializeIntegrationsAndProcessors(
158159
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
159160
options.setTransportGate(new AndroidTransportGate(options));
160161

161-
if (options.isProfilingEnabled()) {
162-
options.setContinuousProfiler(NoOpContinuousProfiler.getInstance());
163-
// Check if the profiler was already instantiated in the app start.
164-
// We use the Android profiler, that uses a global start/stop api, so we need to preserve the
165-
// state of the profiler, and it's only possible retaining the instance.
166-
try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) {
167-
final @Nullable ITransactionProfiler appStartProfiler =
168-
AppStartMetrics.getInstance().getAppStartProfiler();
169-
if (appStartProfiler != null) {
170-
options.setTransactionProfiler(appStartProfiler);
171-
AppStartMetrics.getInstance().setAppStartProfiler(null);
172-
} else {
173-
options.setTransactionProfiler(
174-
new AndroidTransactionProfiler(
175-
context,
176-
options,
177-
buildInfoProvider,
178-
Objects.requireNonNull(
179-
options.getFrameMetricsCollector(),
180-
"options.getFrameMetricsCollector is required")));
181-
}
182-
}
183-
} else {
184-
options.setTransactionProfiler(NoOpTransactionProfiler.getInstance());
185-
// todo handle app start continuous profiler
186-
options.setContinuousProfiler(
187-
new AndroidContinuousProfiler(
188-
buildInfoProvider,
189-
Objects.requireNonNull(
190-
options.getFrameMetricsCollector(),
191-
"options.getFrameMetricsCollector is required"),
192-
options.getLogger(),
193-
options.getProfilingTracesDirPath(),
194-
options.getProfilingTracesHz(),
195-
options.getExecutorService()));
162+
// Check if the profiler was already instantiated in the app start.
163+
// We use the Android profiler, that uses a global start/stop api, so we need to preserve the
164+
// state of the profiler, and it's only possible retaining the instance.
165+
final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance();
166+
final @Nullable ITransactionProfiler appStartTransactionProfiler;
167+
final @Nullable IContinuousProfiler appStartContinuousProfiler;
168+
try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) {
169+
appStartTransactionProfiler = appStartMetrics.getAppStartProfiler();
170+
appStartContinuousProfiler = appStartMetrics.getAppStartContinuousProfiler();
171+
appStartMetrics.setAppStartProfiler(null);
172+
appStartMetrics.setAppStartContinuousProfiler(null);
196173
}
197174

175+
setupProfiler(
176+
options,
177+
context,
178+
buildInfoProvider,
179+
appStartTransactionProfiler,
180+
appStartContinuousProfiler);
181+
198182
options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));
199183
options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger()));
200184

@@ -252,6 +236,56 @@ static void initializeIntegrationsAndProcessors(
252236
}
253237
}
254238

239+
/** Setup the correct profiler (transaction or continuous) based on the options. */
240+
private static void setupProfiler(
241+
final @NotNull SentryAndroidOptions options,
242+
final @NotNull Context context,
243+
final @NotNull BuildInfoProvider buildInfoProvider,
244+
final @Nullable ITransactionProfiler appStartTransactionProfiler,
245+
final @Nullable IContinuousProfiler appStartContinuousProfiler) {
246+
if (options.isProfilingEnabled() || options.getProfilesSampleRate() != null) {
247+
options.setContinuousProfiler(NoOpContinuousProfiler.getInstance());
248+
// This is a safeguard, but it should never happen, as the app start profiler should be the
249+
// continuous one.
250+
if (appStartContinuousProfiler != null) {
251+
appStartContinuousProfiler.close();
252+
}
253+
if (appStartTransactionProfiler != null) {
254+
options.setTransactionProfiler(appStartTransactionProfiler);
255+
} else {
256+
options.setTransactionProfiler(
257+
new AndroidTransactionProfiler(
258+
context,
259+
options,
260+
buildInfoProvider,
261+
Objects.requireNonNull(
262+
options.getFrameMetricsCollector(),
263+
"options.getFrameMetricsCollector is required")));
264+
}
265+
} else {
266+
options.setTransactionProfiler(NoOpTransactionProfiler.getInstance());
267+
// This is a safeguard, but it should never happen, as the app start profiler should be the
268+
// transaction one.
269+
if (appStartTransactionProfiler != null) {
270+
appStartTransactionProfiler.close();
271+
}
272+
if (appStartContinuousProfiler != null) {
273+
options.setContinuousProfiler(appStartContinuousProfiler);
274+
} else {
275+
options.setContinuousProfiler(
276+
new AndroidContinuousProfiler(
277+
buildInfoProvider,
278+
Objects.requireNonNull(
279+
options.getFrameMetricsCollector(),
280+
"options.getFrameMetricsCollector is required"),
281+
options.getLogger(),
282+
options.getProfilingTracesDirPath(),
283+
options.getProfilingTracesHz(),
284+
options.getExecutorService()));
285+
}
286+
}
287+
}
288+
255289
static void installDefaultIntegrations(
256290
final @NotNull Context context,
257291
final @NotNull SentryAndroidOptions options,

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

+65-28
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import android.os.Process;
1313
import android.os.SystemClock;
1414
import androidx.annotation.NonNull;
15+
import io.sentry.IContinuousProfiler;
1516
import io.sentry.ILogger;
1617
import io.sentry.ISentryLifecycleToken;
1718
import io.sentry.ITransactionProfiler;
@@ -100,6 +101,11 @@ public void shutdown() {
100101
if (appStartProfiler != null) {
101102
appStartProfiler.close();
102103
}
104+
final @Nullable IContinuousProfiler appStartContinuousProfiler =
105+
AppStartMetrics.getInstance().getAppStartContinuousProfiler();
106+
if (appStartContinuousProfiler != null) {
107+
appStartContinuousProfiler.close();
108+
}
103109
}
104110
}
105111

@@ -132,40 +138,18 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri
132138
return;
133139
}
134140

141+
if (profilingOptions.isContinuousProfilingEnabled()) {
142+
createAndStartContinuousProfiler(context, profilingOptions, appStartMetrics);
143+
return;
144+
}
145+
135146
if (!profilingOptions.isProfilingEnabled()) {
136147
logger.log(
137148
SentryLevel.INFO, "Profiling is not enabled. App start profiling will not start.");
138149
return;
139150
}
140151

141-
final @NotNull TracesSamplingDecision appStartSamplingDecision =
142-
new TracesSamplingDecision(
143-
profilingOptions.isTraceSampled(),
144-
profilingOptions.getTraceSampleRate(),
145-
profilingOptions.isProfileSampled(),
146-
profilingOptions.getProfileSampleRate());
147-
// We store any sampling decision, so we can respect it when the first transaction starts
148-
appStartMetrics.setAppStartSamplingDecision(appStartSamplingDecision);
149-
150-
if (!(appStartSamplingDecision.getProfileSampled()
151-
&& appStartSamplingDecision.getSampled())) {
152-
logger.log(SentryLevel.DEBUG, "App start profiling was not sampled. It will not start.");
153-
return;
154-
}
155-
logger.log(SentryLevel.DEBUG, "App start profiling started.");
156-
157-
final @NotNull ITransactionProfiler appStartProfiler =
158-
new AndroidTransactionProfiler(
159-
context,
160-
buildInfoProvider,
161-
new SentryFrameMetricsCollector(context, logger, buildInfoProvider),
162-
logger,
163-
profilingOptions.getProfilingTracesDirPath(),
164-
profilingOptions.isProfilingEnabled(),
165-
profilingOptions.getProfilingTracesHz(),
166-
new SentryExecutorService());
167-
appStartMetrics.setAppStartProfiler(appStartProfiler);
168-
appStartProfiler.start();
152+
createAndStartTransactionProfiler(context, profilingOptions, appStartMetrics);
169153

170154
} catch (FileNotFoundException e) {
171155
logger.log(SentryLevel.ERROR, "App start profiling config file not found. ", e);
@@ -174,6 +158,59 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri
174158
}
175159
}
176160

161+
private void createAndStartContinuousProfiler(
162+
final @NotNull Context context,
163+
final @NotNull SentryAppStartProfilingOptions profilingOptions,
164+
final @NotNull AppStartMetrics appStartMetrics) {
165+
final @NotNull IContinuousProfiler appStartContinuousProfiler =
166+
new AndroidContinuousProfiler(
167+
buildInfoProvider,
168+
new SentryFrameMetricsCollector(
169+
context.getApplicationContext(), logger, buildInfoProvider),
170+
logger,
171+
profilingOptions.getProfilingTracesDirPath(),
172+
profilingOptions.getProfilingTracesHz(),
173+
new SentryExecutorService());
174+
appStartMetrics.setAppStartProfiler(null);
175+
appStartMetrics.setAppStartContinuousProfiler(appStartContinuousProfiler);
176+
logger.log(SentryLevel.DEBUG, "App start continuous profiling started.");
177+
appStartContinuousProfiler.start();
178+
}
179+
180+
private void createAndStartTransactionProfiler(
181+
final @NotNull Context context,
182+
final @NotNull SentryAppStartProfilingOptions profilingOptions,
183+
final @NotNull AppStartMetrics appStartMetrics) {
184+
final @NotNull TracesSamplingDecision appStartSamplingDecision =
185+
new TracesSamplingDecision(
186+
profilingOptions.isTraceSampled(),
187+
profilingOptions.getTraceSampleRate(),
188+
profilingOptions.isProfileSampled(),
189+
profilingOptions.getProfileSampleRate());
190+
// We store any sampling decision, so we can respect it when the first transaction starts
191+
appStartMetrics.setAppStartSamplingDecision(appStartSamplingDecision);
192+
193+
if (!(appStartSamplingDecision.getProfileSampled() && appStartSamplingDecision.getSampled())) {
194+
logger.log(SentryLevel.DEBUG, "App start profiling was not sampled. It will not start.");
195+
return;
196+
}
197+
198+
final @NotNull ITransactionProfiler appStartProfiler =
199+
new AndroidTransactionProfiler(
200+
context,
201+
buildInfoProvider,
202+
new SentryFrameMetricsCollector(context, logger, buildInfoProvider),
203+
logger,
204+
profilingOptions.getProfilingTracesDirPath(),
205+
profilingOptions.isProfilingEnabled(),
206+
profilingOptions.getProfilingTracesHz(),
207+
new SentryExecutorService());
208+
appStartMetrics.setAppStartContinuousProfiler(null);
209+
appStartMetrics.setAppStartProfiler(appStartProfiler);
210+
logger.log(SentryLevel.DEBUG, "App start profiling started.");
211+
appStartProfiler.start();
212+
}
213+
177214
@SuppressLint("NewApi")
178215
private void onAppLaunched(
179216
final @Nullable Context context, final @NotNull AppStartMetrics appStartMetrics) {

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import androidx.annotation.NonNull;
1111
import androidx.annotation.Nullable;
1212
import androidx.annotation.VisibleForTesting;
13+
import io.sentry.IContinuousProfiler;
1314
import io.sentry.ISentryLifecycleToken;
1415
import io.sentry.ITransactionProfiler;
1516
import io.sentry.SentryDate;
@@ -57,6 +58,7 @@ public enum AppStartType {
5758
private final @NotNull Map<ContentProvider, TimeSpan> contentProviderOnCreates;
5859
private final @NotNull List<ActivityLifecycleTimeSpan> activityLifecycles;
5960
private @Nullable ITransactionProfiler appStartProfiler = null;
61+
private @Nullable IContinuousProfiler appStartContinuousProfiler = null;
6062
private @Nullable TracesSamplingDecision appStartSamplingDecision = null;
6163
private @Nullable SentryDate onCreateTime = null;
6264
private boolean appLaunchTooLong = false;
@@ -186,6 +188,10 @@ public void clear() {
186188
appStartProfiler.close();
187189
}
188190
appStartProfiler = null;
191+
if (appStartContinuousProfiler != null) {
192+
appStartContinuousProfiler.close();
193+
}
194+
appStartContinuousProfiler = null;
189195
appStartSamplingDecision = null;
190196
appLaunchTooLong = false;
191197
appLaunchedInForeground = false;
@@ -201,6 +207,15 @@ public void setAppStartProfiler(final @Nullable ITransactionProfiler appStartPro
201207
this.appStartProfiler = appStartProfiler;
202208
}
203209

210+
public @Nullable IContinuousProfiler getAppStartContinuousProfiler() {
211+
return appStartContinuousProfiler;
212+
}
213+
214+
public void setAppStartContinuousProfiler(
215+
final @Nullable IContinuousProfiler appStartContinuousProfiler) {
216+
this.appStartContinuousProfiler = appStartContinuousProfiler;
217+
}
218+
204219
public void setAppStartSamplingDecision(
205220
final @Nullable TracesSamplingDecision appStartSamplingDecision) {
206221
this.appStartSamplingDecision = appStartSamplingDecision;
@@ -259,11 +274,15 @@ private void checkCreateTimeOnMain(final @NotNull Application application) {
259274
if (onCreateTime == null) {
260275
appLaunchedInForeground = false;
261276

262-
// we stop the app start profiler, as it's useless and likely to timeout
277+
// we stop the app start profilers, as they are useless and likely to timeout
263278
if (appStartProfiler != null && appStartProfiler.isRunning()) {
264279
appStartProfiler.close();
265280
appStartProfiler = null;
266281
}
282+
if (appStartContinuousProfiler != null && appStartContinuousProfiler.isRunning()) {
283+
appStartContinuousProfiler.close();
284+
appStartContinuousProfiler = null;
285+
}
267286
}
268287
application.unregisterActivityLifecycleCallbacks(instance);
269288
});

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import io.sentry.IScopes
1111
import io.sentry.ISentryExecutorService
1212
import io.sentry.MemoryCollectionData
1313
import io.sentry.PerformanceCollectionData
14+
import io.sentry.Sentry
1415
import io.sentry.SentryLevel
1516
import io.sentry.SentryNanotimeDate
1617
import io.sentry.SentryTracer
@@ -80,7 +81,7 @@ class AndroidContinuousProfilerTest {
8081
options.profilingTracesDirPath,
8182
options.profilingTracesHz,
8283
options.executorService
83-
).also { it.setScopes(scopes) }
84+
)
8485
}
8586
}
8687

@@ -118,6 +119,8 @@ class AndroidContinuousProfilerTest {
118119
// Profiler doesn't start if the folder doesn't exists.
119120
// Usually it's generated when calling Sentry.init, but for tests we can create it manually.
120121
File(fixture.options.profilingTracesDirPath!!).mkdirs()
122+
123+
Sentry.setCurrentScopes(fixture.scopes)
121124
}
122125

123126
@AfterTest

0 commit comments

Comments
 (0)