Skip to content

Commit

Permalink
Instantiate continuous profiling v8 (p3) (#3725)
Browse files Browse the repository at this point in the history
* added profile context to SentryTracer
* removed isProfilingEnabled from AndroidContinuousProfiler, as it's useless
* added continuous profiler to SentryOptions
* added DefaultTransactionPerformanceCollector to AndroidContinuousProfiler
* updated DefaultTransactionPerformanceCollector to work with string ids other than transactions
* fixed ProfileChunk measurements being modifiable from other code
* added thread id and name to SpanContext.data
* added profiler_id to span data
* close continuous profiler on scopes close
* renamed TransactionPerformanceCollector to CompositePerformanceCollector
* added SpanContext.data ser/deser

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
  • Loading branch information
stefanosiano authored Nov 14, 2024
1 parent 3c9a35a commit 5ebb536
Show file tree
Hide file tree
Showing 72 changed files with 1,217 additions and 249 deletions.
6 changes: 4 additions & 2 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ public final class io/sentry/android/core/ActivityLifecycleIntegration : android
}

public class io/sentry/android/core/AndroidContinuousProfiler : io/sentry/IContinuousProfiler {
public fun <init> (Lio/sentry/android/core/BuildInfoProvider;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;Lio/sentry/ILogger;Ljava/lang/String;ZILio/sentry/ISentryExecutorService;)V
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
public fun close ()V
public fun getProfilerId ()Lio/sentry/protocol/SentryId;
public fun isRunning ()Z
public fun setScopes (Lio/sentry/IScopes;)V
public fun start ()V
public fun stop ()V
}
Expand Down Expand Up @@ -447,6 +447,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V
public fun clear ()V
public fun getActivityLifecycleTimeSpans ()Ljava/util/List;
public fun getAppStartContinuousProfiler ()Lio/sentry/IContinuousProfiler;
public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler;
public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getAppStartTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
Expand All @@ -465,6 +466,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V
public fun registerApplicationForegroundCheck (Landroid/app/Application;)V
public fun setAppLaunchedInForeground (Z)V
public fun setAppStartContinuousProfiler (Lio/sentry/IContinuousProfiler;)V
public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V
public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@

import android.annotation.SuppressLint;
import android.os.Build;
import io.sentry.CompositePerformanceCollector;
import io.sentry.IContinuousProfiler;
import io.sentry.ILogger;
import io.sentry.IScopes;
import io.sentry.ISentryExecutorService;
import io.sentry.NoOpScopes;
import io.sentry.PerformanceCollectionData;
import io.sentry.ProfileChunk;
import io.sentry.Sentry;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
Expand All @@ -28,7 +32,6 @@ public class AndroidContinuousProfiler implements IContinuousProfiler {

private final @NotNull ILogger logger;
private final @Nullable String profilingTracesDirPath;
private final boolean isProfilingEnabled;
private final int profilingTracesHz;
private final @NotNull ISentryExecutorService executorService;
private final @NotNull BuildInfoProvider buildInfoProvider;
Expand All @@ -37,7 +40,8 @@ public class AndroidContinuousProfiler implements IContinuousProfiler {
private @Nullable AndroidProfiler profiler = null;
private boolean isRunning = false;
private @Nullable IScopes scopes;
private @Nullable Future<?> closeFuture;
private @Nullable Future<?> stopFuture;
private @Nullable CompositePerformanceCollector performanceCollector;
private final @NotNull List<ProfileChunk.Builder> payloadBuilders = new ArrayList<>();
private @NotNull SentryId profilerId = SentryId.EMPTY_ID;
private @NotNull SentryId chunkId = SentryId.EMPTY_ID;
Expand All @@ -47,14 +51,12 @@ public AndroidContinuousProfiler(
final @NotNull SentryFrameMetricsCollector frameMetricsCollector,
final @NotNull ILogger logger,
final @Nullable String profilingTracesDirPath,
final boolean isProfilingEnabled,
final int profilingTracesHz,
final @NotNull ISentryExecutorService executorService) {
this.logger = logger;
this.frameMetricsCollector = frameMetricsCollector;
this.buildInfoProvider = buildInfoProvider;
this.profilingTracesDirPath = profilingTracesDirPath;
this.isProfilingEnabled = isProfilingEnabled;
this.profilingTracesHz = profilingTracesHz;
this.executorService = executorService;
}
Expand All @@ -65,10 +67,6 @@ private void init() {
return;
}
isInitialized = true;
if (!isProfilingEnabled) {
logger.log(SentryLevel.INFO, "Profiling is disabled in options.");
return;
}
if (profilingTracesDirPath == null) {
logger.log(
SentryLevel.WARNING,
Expand All @@ -92,11 +90,14 @@ private void init() {
logger);
}

public synchronized void setScopes(final @NotNull IScopes scopes) {
this.scopes = scopes;
}

public synchronized void start() {
if ((scopes == null || scopes != NoOpScopes.getInstance())
&& Sentry.getCurrentScopes() != NoOpScopes.getInstance()) {
this.scopes = Sentry.getCurrentScopes();
this.performanceCollector =
Sentry.getCurrentScopes().getOptions().getCompositePerformanceCollector();
}

// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;
Expand Down Expand Up @@ -124,8 +125,12 @@ public synchronized void start() {
chunkId = new SentryId();
}

if (performanceCollector != null) {
performanceCollector.start(chunkId.toString());
}

try {
closeFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS);
stopFuture = executorService.schedule(() -> stop(true), MAX_CHUNK_DURATION_MILLIS);
} catch (RejectedExecutionException e) {
logger.log(
SentryLevel.ERROR,
Expand All @@ -140,8 +145,8 @@ public synchronized void stop() {

@SuppressLint("NewApi")
private synchronized void stop(final boolean restartProfiler) {
if (closeFuture != null) {
closeFuture.cancel(true);
if (stopFuture != null) {
stopFuture.cancel(true);
}
// check if profiler was created and it's running
if (profiler == null || !isRunning) {
Expand All @@ -154,8 +159,13 @@ private synchronized void stop(final boolean restartProfiler) {
return;
}

// todo add PerformanceCollectionData
final AndroidProfiler.ProfileEndData endData = profiler.endAndCollect(false, null);
List<PerformanceCollectionData> performanceCollectionData = null;
if (performanceCollector != null) {
performanceCollectionData = performanceCollector.stop(chunkId.toString());
}

final AndroidProfiler.ProfileEndData endData =
profiler.endAndCollect(false, performanceCollectionData);

// check if profiler end successfully
if (endData == null) {
Expand Down Expand Up @@ -195,6 +205,11 @@ public synchronized void close() {
stop();
}

@Override
public @NotNull SentryId getProfilerId() {
return profilerId;
}

private void sendChunks(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
try {
options
Expand Down Expand Up @@ -224,7 +239,7 @@ public boolean isRunning() {

@VisibleForTesting
@Nullable
Future<?> getCloseFuture() {
return closeFuture;
Future<?> getStopFuture() {
return stopFuture;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import io.sentry.DeduplicateMultithreadedEventProcessor;
import io.sentry.DefaultTransactionPerformanceCollector;
import io.sentry.DefaultCompositePerformanceCollector;
import io.sentry.IContinuousProfiler;
import io.sentry.ILogger;
import io.sentry.ISentryLifecycleToken;
import io.sentry.ITransactionProfiler;
import io.sentry.NoOpConnectionStatusProvider;
import io.sentry.NoOpContinuousProfiler;
import io.sentry.NoOpTransactionProfiler;
import io.sentry.ScopeType;
import io.sentry.SendFireAndForgetEnvelopeSender;
import io.sentry.SendFireAndForgetOutboxSender;
Expand Down Expand Up @@ -159,23 +162,23 @@ static void initializeIntegrationsAndProcessors(
// Check if the profiler was already instantiated in the app start.
// We use the Android profiler, that uses a global start/stop api, so we need to preserve the
// state of the profiler, and it's only possible retaining the instance.
final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance();
final @Nullable ITransactionProfiler appStartTransactionProfiler;
final @Nullable IContinuousProfiler appStartContinuousProfiler;
try (final @NotNull ISentryLifecycleToken ignored = AppStartMetrics.staticLock.acquire()) {
final @Nullable ITransactionProfiler appStartProfiler =
AppStartMetrics.getInstance().getAppStartProfiler();
if (appStartProfiler != null) {
options.setTransactionProfiler(appStartProfiler);
AppStartMetrics.getInstance().setAppStartProfiler(null);
} else {
options.setTransactionProfiler(
new AndroidTransactionProfiler(
context,
options,
buildInfoProvider,
Objects.requireNonNull(
options.getFrameMetricsCollector(),
"options.getFrameMetricsCollector is required")));
}
appStartTransactionProfiler = appStartMetrics.getAppStartProfiler();
appStartContinuousProfiler = appStartMetrics.getAppStartContinuousProfiler();
appStartMetrics.setAppStartProfiler(null);
appStartMetrics.setAppStartContinuousProfiler(null);
}

setupProfiler(
options,
context,
buildInfoProvider,
appStartTransactionProfiler,
appStartContinuousProfiler);

options.setModulesLoader(new AssetsModulesLoader(context, options.getLogger()));
options.setDebugMetaLoader(new AssetsDebugMetaLoader(context, options.getLogger()));

Expand Down Expand Up @@ -223,7 +226,7 @@ static void initializeIntegrationsAndProcessors(
"options.getFrameMetricsCollector is required")));
}
}
options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options));
options.setCompositePerformanceCollector(new DefaultCompositePerformanceCollector(options));

if (options.getCacheDirPath() != null) {
if (options.isEnableScopePersistence()) {
Expand All @@ -233,6 +236,56 @@ static void initializeIntegrationsAndProcessors(
}
}

/** Setup the correct profiler (transaction or continuous) based on the options. */
private static void setupProfiler(
final @NotNull SentryAndroidOptions options,
final @NotNull Context context,
final @NotNull BuildInfoProvider buildInfoProvider,
final @Nullable ITransactionProfiler appStartTransactionProfiler,
final @Nullable IContinuousProfiler appStartContinuousProfiler) {
if (options.isProfilingEnabled() || options.getProfilesSampleRate() != null) {
options.setContinuousProfiler(NoOpContinuousProfiler.getInstance());
// This is a safeguard, but it should never happen, as the app start profiler should be the
// continuous one.
if (appStartContinuousProfiler != null) {
appStartContinuousProfiler.close();
}
if (appStartTransactionProfiler != null) {
options.setTransactionProfiler(appStartTransactionProfiler);
} else {
options.setTransactionProfiler(
new AndroidTransactionProfiler(
context,
options,
buildInfoProvider,
Objects.requireNonNull(
options.getFrameMetricsCollector(),
"options.getFrameMetricsCollector is required")));
}
} else {
options.setTransactionProfiler(NoOpTransactionProfiler.getInstance());
// This is a safeguard, but it should never happen, as the app start profiler should be the
// transaction one.
if (appStartTransactionProfiler != null) {
appStartTransactionProfiler.close();
}
if (appStartContinuousProfiler != null) {
options.setContinuousProfiler(appStartContinuousProfiler);
} else {
options.setContinuousProfiler(
new AndroidContinuousProfiler(
buildInfoProvider,
Objects.requireNonNull(
options.getFrameMetricsCollector(),
"options.getFrameMetricsCollector is required"),
options.getLogger(),
options.getProfilingTracesDirPath(),
options.getProfilingTracesHz(),
options.getExecutorService()));
}
}
}

static void installDefaultIntegrations(
final @NotNull Context context,
final @NotNull SentryAndroidOptions options,
Expand Down
Loading

0 comments on commit 5ebb536

Please sign in to comment.