Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9a60ad1
feat(profiling): Add useProfilingManager option
43jay Mar 27, 2026
1250aa1
chore(samples): Update sample app to support Perfetto profiling testing
43jay Mar 27, 2026
05d73b1
chore(samples): Improve ProfilingActivity UI for Perfetto testing
43jay Apr 7, 2026
5f894d0
feat(profiling): Skip legacy app-start profiling when useProfilingManโ€ฆ
43jay Mar 27, 2026
c2a4442
feat(profiling): Add PerfettoProfiler and wire into AndroidContinuousโ€ฆ
43jay Mar 30, 2026
bc2e59d
fix(profiling): Fix meta_length not serialized in Perfetto envelope hโ€ฆ
43jay Apr 9, 2026
a53c383
ref(profiling): Extract PerfettoContinuousProfiler from AndroidContinโ€ฆ
43jay Apr 1, 2026
dd2c5a4
test(profiling): Extract shared profiler test cases into ContinuousPrโ€ฆ
43jay Apr 10, 2026
6ebea19
test(profiling): Wire PerfettoContinuousProfilerTest to shared test cโ€ฆ
43jay Apr 1, 2026
5e4bead
ref(profiling): Remove app-start profiling logic from PerfettoContinuโ€ฆ
43jay Apr 7, 2026
443cc45
ref(profiling): Improve thread safety in PerfettoContinuousProfiler
43jay Apr 7, 2026
dcee449
chore: Run spotlessApply and apiDump
43jay Apr 7, 2026
331622b
ref(profiling): Move frame metrics collection from PerfettoProfiler tโ€ฆ
43jay Apr 10, 2026
85e54fc
ref(profiling): Consolidate measurement collection into ChunkMeasuremโ€ฆ
43jay Apr 10, 2026
11331ea
fix(profiling): Move shouldStop reset inside !isRunning() guard
43jay Apr 13, 2026
4c48101
Format code
getsentry-bot Apr 13, 2026
35b2bcb
fix(profiling): Add API level guard for PerfettoContinuousProfiler
43jay Apr 13, 2026
268a1e0
fix(profiling): Snapshot frame measurement deques before async serialโ€ฆ
43jay Apr 17, 2026
ffd5c6b
Format code
getsentry-bot Apr 17, 2026
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
21 changes: 20 additions & 1 deletion sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
}

public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler, io/sentry/transport/RateLimiter$IRateLimitObserver {
public fun <init> (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/util/LazyEvaluator$Evaluator;)V
public fun close (Z)V
public static fun createLegacy (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ILio/sentry/util/LazyEvaluator$Evaluator;)Lio/sentry/android/core/AndroidContinuousProfiler;
public fun getChunkId ()Lio/sentry/protocol/SentryId;
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
public fun getRootSpanCounter ()I
Expand Down Expand Up @@ -338,6 +338,25 @@ public final class io/sentry/android/core/NetworkBreadcrumbsIntegration : io/sen
public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V
}

public class io/sentry/android/core/PerfettoContinuousProfiler : io/sentry/IContinuousProfiler, io/sentry/transport/RateLimiter$IRateLimitObserver {
public fun <init> (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/ILogger;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/util/LazyEvaluator$Evaluator;Lio/sentry/util/LazyEvaluator$Evaluator;)V
public fun close (Z)V
public fun getActiveTraceCount ()I
public fun getChunkId ()Lio/sentry/protocol/SentryId;
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
public fun isRunning ()Z
public fun onRateLimitChanged (Lio/sentry/transport/RateLimiter;)V
public fun reevaluateSampling ()V
public fun startProfiler (Lio/sentry/ProfileLifecycle;Lio/sentry/TracesSampler;)V
public fun stopProfiler (Lio/sentry/ProfileLifecycle;)V
}

public class io/sentry/android/core/PerfettoProfiler {
public fun <init> (Landroid/content/Context;Lio/sentry/ILogger;)V
public fun endAndCollect ()Ljava/io/File;
public fun start (J)Z
}

public final class io/sentry/android/core/ScreenshotEventProcessor : io/sentry/EventProcessor {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;Z)V
public fun getOrder ()Ljava/lang/Long;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,29 @@ public class AndroidContinuousProfiler
private final AutoClosableReentrantLock lock = new AutoClosableReentrantLock();
private final AutoClosableReentrantLock payloadLock = new AutoClosableReentrantLock();

public AndroidContinuousProfiler(
public static AndroidContinuousProfiler createLegacy(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need that, I'd keep the public constructor as-is instead. Instead let's add a comment to outline that this profiler is considered legacy.

final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
final @NotNull ILogger logger,
final @Nullable String profilingTracesDirPath,
final int profilingTracesHz,
final @NotNull LazyEvaluator.Evaluator<ISentryExecutorService> executorServiceSupplier) {
return new AndroidContinuousProfiler(
buildInfoProvider,
frameMetricsCollector,
executorServiceSupplier,
logger,
profilingTracesHz,
profilingTracesDirPath);
}

private AndroidContinuousProfiler(
final @NotNull BuildInfoProvider buildInfoProvider,
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
final @NotNull LazyEvaluator.Evaluator<ISentryExecutorService> executorServiceSupplier,
final @NotNull ILogger logger,
final int profilingTracesHz,
final @Nullable String profilingTracesDirPath) {
this.logger = logger;
this.frameMetricsCollector = frameMetricsCollector;
this.buildInfoProvider = buildInfoProvider;
Expand All @@ -89,6 +105,7 @@ private void init() {
return;
}
isInitialized = true;

if (profilingTracesDirPath == null) {
logger.log(
SentryLevel.WARNING,
Expand Down Expand Up @@ -152,21 +169,24 @@ public void startProfiler(
}
}

private void initScopes() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't think we need this change at all. Let's keep the legacy code as-is.

private void tryResolveScopes() {
if ((scopes == null || scopes == NoOpScopes.getInstance())
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
this.scopes = Sentry.getCurrentScopes();
this.performanceCollector =
Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector();
final @Nullable RateLimiter rateLimiter = scopes.getRateLimiter();
if (rateLimiter != null) {
rateLimiter.addRateLimitObserver(this);
}
onScopesAvailable(Sentry.getCurrentScopes());
}
}

private void onScopesAvailable(final @NotNull IScopes resolvedScopes) {
this.scopes = resolvedScopes;
this.performanceCollector = resolvedScopes.getOptions().getCompositePerformanceCollector();
final @Nullable RateLimiter rateLimiter = resolvedScopes.getRateLimiter();
if (rateLimiter != null) {
rateLimiter.addRateLimitObserver(this);
}
}

private void start() {
initScopes();
tryResolveScopes();

// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
Expand Down Expand Up @@ -259,7 +279,7 @@ public void stopProfiler(final @NotNull ProfileLifecycle profileLifecycle) {
}

private void stop(final boolean restartProfiler) {
initScopes();
tryResolveScopes();
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
if (stopFuture != null) {
stopFuture.cancel(true);
Expand Down Expand Up @@ -297,14 +317,15 @@ private void stop(final boolean restartProfiler) {
// start profiling), meaning there's no scopes to send the chunks. In that case, we store
// the data in a list and send it when the next chunk is finished.
try (final @NotNull ISentryLifecycleToken ignored2 = payloadLock.acquire()) {
payloadBuilders.add(
final ProfileChunk.Builder builder =
new ProfileChunk.Builder(
profilerId,
chunkId,
endData.measurementsMap,
endData.traceFile,
startProfileChunkTimestamp,
ProfileChunk.PLATFORM_ANDROID));
ProfileChunk.PLATFORM_ANDROID);
payloadBuilders.add(builder);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
Expand Down Expand Up @@ -293,6 +294,7 @@ static void initializeIntegrationsAndProcessors(
}

/** Setup the correct profiler (transaction or continuous) based on the options. */
@SuppressLint("NewApi")
private static void setupProfiler(
final @NotNull SentryAndroidOptions options,
final @NotNull Context context,
Expand Down Expand Up @@ -335,16 +337,38 @@ private static void setupProfiler(
performanceCollector.start(chunkId.toString());
}
} else {
options.setContinuousProfiler(
new AndroidContinuousProfiler(
buildInfoProvider,
Objects.requireNonNull(
options.getFrameMetricsCollector(),
"options.getFrameMetricsCollector is required"),
options.getLogger(),
options.getProfilingTracesDirPath(),
options.getProfilingTracesHz(),
() -> options.getExecutorService()));
final @NotNull SentryFrameMetricsCollector frameMetricsCollector =
Objects.requireNonNull(
options.getFrameMetricsCollector(), "options.getFrameMetricsCollector is required");
if (options.isUseProfilingManager()) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
options.setContinuousProfiler(
new PerfettoContinuousProfiler(
buildInfoProvider,
options.getLogger(),
frameMetricsCollector,
() -> options.getExecutorService(),
() ->
new PerfettoProfiler(
context.getApplicationContext(), options.getLogger())));
} else {
options
.getLogger()
.log(
SentryLevel.WARNING,
"useProfilingManager is enabled but requires API 35+. "
+ "No profiling data will be collected.");
}
} else {
options.setContinuousProfiler(
AndroidContinuousProfiler.createLegacy(
buildInfoProvider,
frameMetricsCollector,
options.getLogger(),
options.getProfilingTracesDirPath(),
options.getProfilingTracesHz(),
() -> options.getExecutorService()));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ final class ManifestMetadataReader {

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

static final String USE_PROFILING_MANAGER = "io.sentry.profiling.use-profiling-manager";

static final String ENABLE_SCOPE_PERSISTENCE = "io.sentry.enable-scope-persistence";

static final String REPLAYS_SESSION_SAMPLE_RATE = "io.sentry.session-replay.session-sample-rate";
Expand Down Expand Up @@ -497,6 +499,9 @@ static void applyMetadata(
readBool(
metadata, logger, ENABLE_APP_START_PROFILING, options.isEnableAppStartProfiling()));

options.setUseProfilingManager(
readBool(metadata, logger, USE_PROFILING_MANAGER, options.isUseProfilingManager()));

options.setEnableScopePersistence(
readBool(
metadata, logger, ENABLE_SCOPE_PERSISTENCE, options.isEnableScopePersistence()));
Expand Down
Loading