Skip to content

Commit 7055ed1

Browse files
authored
feat(profiling): Add Android ProfilingManager (Perfetto) support (#5251)
1 parent 80c3e67 commit 7055ed1

32 files changed

Lines changed: 2516 additions & 597 deletions

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Use Android's `ProfilingManager` (Perfetto) for continuous profiling on API 35+ devices ([#5251](https://github.com/getsentry/sentry-java/pull/5251))
8+
- On API 35+ devices, continuous profiling now automatically uses Android's system `ProfilingManager` with Perfetto-based stack sampling, providing lower-overhead and more accurate profiles. No configuration change is required.
9+
- Devices below API 35 keep using the legacy `Debug`-based profiler.
10+
- Added an `enableLegacyProfiling` option (default `true`) to disable the legacy `Debug`-based profiler. Setting it to `false` disables continuous profiling on API < 35 devices as well as transaction-based profiling (`profilesSampleRate`/`profilesSampler`) on all devices, since transaction-based profiling is not supported by Perfetto.
11+
- It can also be configured via the `io.sentry.profiling.enable-legacy-profiling` manifest flag.
12+
- See the [Android profiling docs](https://docs.sentry.io/platforms/android/profiling/) for details.
13+
514
### Behavioral Changes
615

716
- The outbox and cache directories are no longer created by `Sentry.init` ([#5792](https://github.com/getsentry/sentry-java/pull/5792))

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,24 @@ public final class io/sentry/android/core/NetworkBreadcrumbsIntegration : io/sen
362362
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
363363
}
364364

365+
public class io/sentry/android/core/PerfettoContinuousProfiler : io/sentry/IContinuousProfiler, io/sentry/transport/RateLimiter$IRateLimitObserver {
366+
public fun <init> (Lio/sentry/ILogger;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/util/LazyEvaluator$Evaluator;Ljava/util/function/Supplier;)V
367+
public fun close (Z)V
368+
public fun getChunkId ()Lio/sentry/protocol/SentryId;
369+
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
370+
public fun isRunning ()Z
371+
public fun onRateLimitChanged (Lio/sentry/transport/RateLimiter;)V
372+
public fun reevaluateSampling ()V
373+
public fun startProfiler (Lio/sentry/ProfileLifecycle;Lio/sentry/TracesSampler;)V
374+
public fun stopProfiler (Lio/sentry/ProfileLifecycle;)V
375+
}
376+
377+
public class io/sentry/android/core/PerfettoProfiler {
378+
public fun <init> (Landroid/content/Context;Lio/sentry/ILogger;Lio/sentry/ISentryExecutorService;)V
379+
public fun endAndCollect (Ljava/util/function/Consumer;)V
380+
public fun start (J)Z
381+
}
382+
365383
public final class io/sentry/android/core/ScreenshotEventProcessor : io/sentry/EventProcessor {
366384
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;Z)V
367385
public fun getOrder ()Ljava/lang/Long;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
import org.jetbrains.annotations.Nullable;
3939
import org.jetbrains.annotations.VisibleForTesting;
4040

41+
/**
42+
* Legacy Android implementation of {@link IContinuousProfiler}, using Android's {@code
43+
* Debug.startMethodTracingSampling} See {@link PerfettoContinuousProfiler} for the new
44+
* implementation using {@code ProfilingManager}, available on API 35+.
45+
*/
4146
@ApiStatus.Internal
4247
public class AndroidContinuousProfiler
4348
implements IContinuousProfiler, RateLimiter.IRateLimitObserver {

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

Lines changed: 54 additions & 10 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;
@@ -299,6 +300,7 @@ static void initializeIntegrationsAndProcessors(
299300
}
300301

301302
/** Setup the correct profiler (transaction or continuous) based on the options. */
303+
@SuppressLint("NewApi")
302304
private static void setupProfiler(
303305
final @NotNull SentryAndroidOptions options,
304306
final @NotNull Context context,
@@ -308,6 +310,28 @@ private static void setupProfiler(
308310
final @NotNull CompositePerformanceCollector performanceCollector) {
309311
if (options.isProfilingEnabled() || options.getProfilesSampleRate() != null) {
310312
options.setContinuousProfiler(NoOpContinuousProfiler.getInstance());
313+
// Transaction-based profiling always relies on the legacy Debug-based profiler, so it is
314+
// disabled together with legacy profiling. Perfetto profiling only supports continuous
315+
// profiling.
316+
if (!options.isEnableLegacyProfiling()) {
317+
options
318+
.getLogger()
319+
.log(
320+
SentryLevel.WARNING,
321+
"Transaction-based profiling (profilesSampleRate/profilesSampler) is disabled "
322+
+ "because enableLegacyProfiling is false. Transaction-based profiling always "
323+
+ "uses the legacy profiler and is not supported by Perfetto. No profiling "
324+
+ "data will be collected. Use profileSessionSampleRate for continuous "
325+
+ "profiling instead.");
326+
options.setTransactionProfiler(NoOpTransactionProfiler.getInstance());
327+
if (appStartTransactionProfiler != null) {
328+
appStartTransactionProfiler.close();
329+
}
330+
if (appStartContinuousProfiler != null) {
331+
appStartContinuousProfiler.close(true);
332+
}
333+
return;
334+
}
311335
// This is a safeguard, but it should never happen, as the app start profiler should be the
312336
// continuous one.
313337
if (appStartContinuousProfiler != null) {
@@ -341,16 +365,36 @@ private static void setupProfiler(
341365
performanceCollector.start(chunkId.toString());
342366
}
343367
} else {
344-
options.setContinuousProfiler(
345-
new AndroidContinuousProfiler(
346-
buildInfoProvider,
347-
Objects.requireNonNull(
348-
options.getFrameMetricsCollector(),
349-
"options.getFrameMetricsCollector is required"),
350-
options.getLogger(),
351-
options.getProfilingTracesDirPath(),
352-
options.getProfilingTracesHz(),
353-
() -> options.getExecutorService()));
368+
final @NotNull SentryFrameMetricsCollector frameMetricsCollector =
369+
Objects.requireNonNull(
370+
options.getFrameMetricsCollector(), "options.getFrameMetricsCollector is required");
371+
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
372+
final @NotNull Context appContext = ContextUtils.getApplicationContext(context);
373+
options.setContinuousProfiler(
374+
new PerfettoContinuousProfiler(
375+
options.getLogger(),
376+
frameMetricsCollector,
377+
() -> options.getExecutorService(),
378+
() ->
379+
new PerfettoProfiler(
380+
appContext, options.getLogger(), options.getExecutorService())));
381+
} else if (options.isEnableLegacyProfiling()) {
382+
options.setContinuousProfiler(
383+
new AndroidContinuousProfiler(
384+
buildInfoProvider,
385+
frameMetricsCollector,
386+
options.getLogger(),
387+
options.getProfilingTracesDirPath(),
388+
options.getProfilingTracesHz(),
389+
() -> options.getExecutorService()));
390+
} else {
391+
options
392+
.getLogger()
393+
.log(
394+
SentryLevel.WARNING,
395+
"enableLegacyProfiling is disabled and device is below API 35. "
396+
+ "No profiling data will be collected.");
397+
}
354398
}
355399
}
356400
}

‎sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ final class ManifestMetadataReader {
119119

120120
static final String ENABLE_APP_START_PROFILING = "io.sentry.profiling.enable-app-start";
121121

122+
static final String ENABLE_LEGACY_PROFILING = "io.sentry.profiling.enable-legacy-profiling";
123+
122124
static final String ENABLE_SCOPE_PERSISTENCE = "io.sentry.enable-scope-persistence";
123125

124126
static final String REPLAYS_SESSION_SAMPLE_RATE = "io.sentry.session-replay.session-sample-rate";
@@ -542,6 +544,9 @@ static void applyMetadata(
542544
readBool(
543545
metadata, logger, ENABLE_APP_START_PROFILING, options.isEnableAppStartProfiling()));
544546

547+
options.setEnableLegacyProfiling(
548+
readBool(metadata, logger, ENABLE_LEGACY_PROFILING, options.isEnableLegacyProfiling()));
549+
545550
options.setEnableScopePersistence(
546551
readBool(
547552
metadata, logger, ENABLE_SCOPE_PERSISTENCE, options.isEnableScopePersistence()));

0 commit comments

Comments
 (0)