Skip to content

Commit 3c8e75a

Browse files
committed
ref(profiling): Extract PerfettoContinuousProfiler from AndroidContinuousProfiler
Separate the Perfetto/ProfilingManager profiling backend into its own IContinuousProfiler implementation to keep the two backends independent. - AndroidContinuousProfiler is restored to legacy-only (no Perfetto fields, no conditional branches, no @SuppressLint annotations) - PerfettoContinuousProfiler is a new @RequiresApi(35) class that delegates to PerfettoProfiler and always sets content_type="perfetto" - AndroidOptionsInitializer branches on useProfilingManager to pick the right implementation - Consistent locking: startInternal/stopInternal both require caller to hold the lock, with callers wrapped accordingly - Renamed rootSpanCounter to activeTraceCount in PerfettoContinuousProfiler - Extracted tryResolveScopes/onScopesAvailable from initScopes in both classes - Fixed duplicate listener bug in PerfettoProfiler (was using local lambda instead of class-scope profilingResultListener)
1 parent 8e472c5 commit 3c8e75a

File tree

4 files changed

+417
-117
lines changed

4 files changed

+417
-117
lines changed

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

Lines changed: 20 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import static io.sentry.IConnectionStatusProvider.ConnectionStatus.DISCONNECTED;
55
import static java.util.concurrent.TimeUnit.SECONDS;
66

7-
import android.annotation.SuppressLint;
8-
import android.content.Context;
97
import android.os.Build;
108
import io.sentry.CompositePerformanceCollector;
119
import io.sentry.DataCategory;
@@ -53,10 +51,6 @@ public class AndroidContinuousProfiler
5351
private boolean isInitialized = false;
5452
private final @NotNull SentryFrameMetricsCollector frameMetricsCollector;
5553
private @Nullable AndroidProfiler profiler = null;
56-
private @Nullable PerfettoProfiler perfettoProfiler = null;
57-
private final boolean useProfilingManager;
58-
private final @Nullable Context context;
59-
private boolean isPerfettoActive = false;
6054
private boolean isRunning = false;
6155
private @Nullable IScopes scopes;
6256
private @Nullable Future<?> stopFuture;
@@ -74,10 +68,6 @@ public class AndroidContinuousProfiler
7468
private final AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
7569
private final AutoClosableReentrantLock payloadLock = new AutoClosableReentrantLock();
7670

77-
/**
78-
* Creates a profiler using the legacy Debug.startMethodTracingSampling engine. This is the
79-
* default path used for app-start profiling and devices below API 35.
80-
*/
8171
public static AndroidContinuousProfiler createLegacy(
8272
final @NotNull BuildInfoProvider buildInfoProvider,
8373
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
@@ -90,40 +80,15 @@ public static AndroidContinuousProfiler createLegacy(
9080
frameMetricsCollector,
9181
executorServiceSupplier,
9282
logger,
93-
null,
94-
false,
9583
profilingTracesHz,
9684
profilingTracesDirPath);
9785
}
9886

99-
/**
100-
* Creates a profiler using Android's ProfilingManager (Perfetto) on API 35+. On older devices, no
101-
* profiling data is collected — the legacy Debug-based profiler is not used as a fallback.
102-
*/
103-
public static AndroidContinuousProfiler createWithProfilingManager(
104-
final @NotNull Context context,
105-
final @NotNull BuildInfoProvider buildInfoProvider,
106-
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
107-
final @NotNull ILogger logger,
108-
final @NotNull LazyEvaluator.Evaluator<ISentryExecutorService> executorServiceSupplier) {
109-
return new AndroidContinuousProfiler(
110-
buildInfoProvider,
111-
frameMetricsCollector,
112-
executorServiceSupplier,
113-
logger,
114-
context,
115-
true,
116-
0,
117-
null);
118-
}
119-
12087
private AndroidContinuousProfiler(
12188
final @NotNull BuildInfoProvider buildInfoProvider,
12289
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
12390
final @NotNull LazyEvaluator.Evaluator<ISentryExecutorService> executorServiceSupplier,
12491
final @NotNull ILogger logger,
125-
final @Nullable Context context,
126-
final boolean useProfilingManager,
12792
final int profilingTracesHz,
12893
final @Nullable String profilingTracesDirPath) {
12994
this.logger = logger;
@@ -132,33 +97,15 @@ private AndroidContinuousProfiler(
13297
this.profilingTracesDirPath = profilingTracesDirPath;
13398
this.profilingTracesHz = profilingTracesHz;
13499
this.executorServiceSupplier = executorServiceSupplier;
135-
this.useProfilingManager = useProfilingManager;
136-
this.context = context;
137100
}
138101

139-
@SuppressLint("NewApi")
140102
private void init() {
141-
logger.log(
142-
SentryLevel.DEBUG,
143-
"AndroidContinuousProfiler.init() isInitialized=%s, useProfilingManager=%s, apiLevel=%d",
144-
isInitialized,
145-
useProfilingManager,
146-
buildInfoProvider.getSdkInfoVersion());
147-
148103
// We initialize it only once
149104
if (isInitialized) {
150105
return;
151106
}
152107
isInitialized = true;
153108

154-
if (useProfilingManager) {
155-
initProfilingManager();
156-
} else {
157-
initLegacy();
158-
}
159-
}
160-
161-
private void initLegacy() {
162109
if (profilingTracesDirPath == null) {
163110
logger.log(
164111
SentryLevel.WARNING,
@@ -182,20 +129,6 @@ private void initLegacy() {
182129
logger);
183130
}
184131

185-
@SuppressLint("NewApi")
186-
private void initProfilingManager() {
187-
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM
188-
&& context != null) {
189-
perfettoProfiler = new PerfettoProfiler(context, frameMetricsCollector, logger);
190-
logger.log(SentryLevel.DEBUG, "Using Perfetto profiler (ProfilingManager).");
191-
} else {
192-
logger.log(
193-
SentryLevel.WARNING,
194-
"useProfilingManager requested but not available (requires API 35+). "
195-
+ "No profiling data will be collected.");
196-
}
197-
}
198-
199132
@Override
200133
public void startProfiler(
201134
final @NotNull ProfileLifecycle profileLifecycle,
@@ -236,21 +169,24 @@ public void startProfiler(
236169
}
237170
}
238171

239-
private void initScopes() {
172+
private void tryResolveScopes() {
240173
if ((scopes == null || scopes == NoOpScopes.getInstance())
241174
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
242-
this.scopes = Sentry.getCurrentScopes();
243-
this.performanceCollector =
244-
Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector();
245-
final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter();
246-
if (rateLimiter != null) {
247-
rateLimiter.addRateLimitObserver(this);
248-
}
175+
onScopesAvailable(Sentry.getCurrentScopes());
176+
}
177+
}
178+
179+
private void onScopesAvailable(final @NotNull IScopes resolvedScopes) {
180+
this.scopes = resolvedScopes;
181+
this.performanceCollector = resolvedScopes.getOptions().getCompositePerformanceCollector();
182+
final @Nullable RateLimiter rateLimiter = resolvedScopes.getRateLimiter();
183+
if (rateLimiter != null) {
184+
rateLimiter.addRateLimitObserver(this);
249185
}
250186
}
251187

252188
private void start() {
253-
initScopes();
189+
tryResolveScopes();
254190

255191
// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
256192
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
@@ -259,7 +195,7 @@ private void start() {
259195
// Let's initialize trace folder and profiling interval
260196
init();
261197
// init() didn't create profiler, should never happen
262-
if (profiler == null && perfettoProfiler == null) {
198+
if (profiler == null) {
263199
return;
264200
}
265201

@@ -286,14 +222,7 @@ private void start() {
286222
} else {
287223
startProfileChunkTimestamp = new SentryNanotimeDate();
288224
}
289-
final AndroidProfiler.ProfileStartData startData;
290-
if (perfettoProfiler != null) {
291-
startData = perfettoProfiler.start(MAX_CHUNK_DURATION_MILLIS);
292-
isPerfettoActive = startData != null;
293-
} else {
294-
startData = profiler.start();
295-
isPerfettoActive = false;
296-
}
225+
final AndroidProfiler.ProfileStartData startData = profiler.start();
297226
// check if profiling started
298227
if (startData == null) {
299228
return;
@@ -350,13 +279,13 @@ public void stopProfiler(final @NotNull ProfileLifecycle profileLifecycle) {
350279
}
351280

352281
private void stop(final boolean restartProfiler) {
353-
initScopes();
282+
tryResolveScopes();
354283
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
355284
if (stopFuture != null) {
356-
stopFuture.cancel(false);
285+
stopFuture.cancel(true);
357286
}
358287
// check if profiler was created and it's running
359-
if ((profiler == null && perfettoProfiler == null) || !isRunning) {
288+
if (profiler == null || !isRunning) {
360289
// When the profiler is stopped due to an error (e.g. offline or rate limited), reset the
361290
// ids
362291
profilerId = SentryId.EMPTY_ID;
@@ -375,18 +304,8 @@ private void stop(final boolean restartProfiler) {
375304
performanceCollectionData = performanceCollector.stop(chunkId.toString());
376305
}
377306

378-
final AndroidProfiler.ProfileEndData endData;
379-
final String platform;
380-
if (isPerfettoActive && perfettoProfiler != null) {
381-
endData = perfettoProfiler.endAndCollect();
382-
platform = ProfileChunk.PLATFORM_ANDROID;
383-
} else if (profiler != null) {
384-
endData = profiler.endAndCollect(false, performanceCollectionData);
385-
platform = ProfileChunk.PLATFORM_ANDROID;
386-
} else {
387-
endData = null;
388-
platform = ProfileChunk.PLATFORM_ANDROID;
389-
}
307+
final AndroidProfiler.ProfileEndData endData =
308+
profiler.endAndCollect(false, performanceCollectionData);
390309

391310
// check if profiler end successfully
392311
if (endData == null) {
@@ -405,10 +324,7 @@ private void stop(final boolean restartProfiler) {
405324
endData.measurementsMap,
406325
endData.traceFile,
407326
startProfileChunkTimestamp,
408-
platform);
409-
if (isPerfettoActive) {
410-
builder.setContentType("perfetto");
411-
}
327+
ProfileChunk.PLATFORM_ANDROID);
412328
payloadBuilders.add(builder);
413329
}
414330
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static io.sentry.android.core.NdkIntegration.SENTRY_NDK_CLASS_NAME;
44

5+
import android.annotation.SuppressLint;
56
import android.app.Application;
67
import android.content.Context;
78
import android.content.pm.PackageInfo;
@@ -293,6 +294,7 @@ static void initializeIntegrationsAndProcessors(
293294
}
294295

295296
/** Setup the correct profiler (transaction or continuous) based on the options. */
297+
@SuppressLint("NewApi")
296298
private static void setupProfiler(
297299
final @NotNull SentryAndroidOptions options,
298300
final @NotNull Context context,
@@ -340,12 +342,13 @@ private static void setupProfiler(
340342
options.getFrameMetricsCollector(), "options.getFrameMetricsCollector is required");
341343
options.setContinuousProfiler(
342344
options.isUseProfilingManager()
343-
? AndroidContinuousProfiler.createWithProfilingManager(
344-
context,
345+
? new PerfettoContinuousProfiler(
345346
buildInfoProvider,
346-
frameMetricsCollector,
347347
options.getLogger(),
348-
() -> options.getExecutorService())
348+
() -> options.getExecutorService(),
349+
() ->
350+
new PerfettoProfiler(
351+
context.getApplicationContext(), frameMetricsCollector, options.getLogger()))
349352
: AndroidContinuousProfiler.createLegacy(
350353
buildInfoProvider,
351354
frameMetricsCollector,

0 commit comments

Comments
 (0)