Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Startup Profiling 3 - Add ContentProvider and start profile #3128

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Handle `monitor`/`check_in` in client reports and rate limiter ([#3096](https://github.com/getsentry/sentry-java/pull/3096))
- Startup profiling 1 - Decouple Profiler from Transaction ([#3101](https://github.com/getsentry/sentry-java/pull/3101))
- Startup profiling 2 - Add options and sampling logic ([#3121](https://github.com/getsentry/sentry-java/pull/3121))
- Startup Profiling 3 - Add ContentProvider and start profile ([#3128](https://github.com/getsentry/sentry-java/pull/3128))

### Fixes

Expand Down
14 changes: 12 additions & 2 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun getBeforeViewHierarchyCaptureCallback ()Lio/sentry/android/core/SentryAndroidOptions$BeforeCaptureCallback;
public fun getDebugImagesLoader ()Lio/sentry/android/core/IDebugImagesLoader;
public fun getNativeSdkName ()Ljava/lang/String;
public fun getProfilingTracesHz ()I
public fun getProfilingTracesIntervalMillis ()I
public fun getStartupCrashDurationThresholdMillis ()J
public fun isAnrEnabled ()Z
Expand Down Expand Up @@ -305,7 +304,6 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
public fun setEnableScopeSync (Z)V
public fun setEnableSystemEventBreadcrumbs (Z)V
public fun setNativeSdkName (Ljava/lang/String;)V
public fun setProfilingTracesHz (I)V
public fun setProfilingTracesIntervalMillis (I)V
public fun setReportHistoricalAnrs (Z)V
}
Expand Down Expand Up @@ -347,6 +345,14 @@ public final class io/sentry/android/core/SentryPerformanceProvider {
public fun onCreate ()Z
}

public final class io/sentry/android/core/SentryStartupProfilingProvider {
public fun <init> ()V
public fun attachInfo (Landroid/content/Context;Landroid/content/pm/ProviderInfo;)V
public fun getType (Landroid/net/Uri;)Ljava/lang/String;
public fun onCreate ()Z
public fun shutdown ()V
}

public final class io/sentry/android/core/SystemEventsBreadcrumbsIntegration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun <init> (Landroid/content/Context;Ljava/util/List;)V
Expand Down Expand Up @@ -426,12 +432,16 @@ public class io/sentry/android/core/performance/AppStartMetrics {
public fun getContentProviderOnCreateTimeSpans ()Ljava/util/List;
public static fun getInstance ()Lio/sentry/android/core/performance/AppStartMetrics;
public fun getSdkInitTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
public fun getStartupProfiler ()Lio/sentry/ITransactionProfiler;
public fun getStartupSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun isAppLaunchedInForeground ()Z
public static fun onApplicationCreate (Landroid/app/Application;)V
public static fun onApplicationPostCreate (Landroid/app/Application;)V
public static fun onContentProviderCreate (Landroid/content/ContentProvider;)V
public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V
public fun setStartupProfiler (Lio/sentry/ITransactionProfiler;)V
public fun setStartupSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
}

public final class io/sentry/android/core/performance/AppStartMetrics$AppStartType : java/lang/Enum {
Expand Down
6 changes: 6 additions & 0 deletions sentry-android-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
android:authorities="${applicationId}.SentryInitProvider"
android:exported="false"/>

<provider
stefanosiano marked this conversation as resolved.
Show resolved Hide resolved
android:name=".SentryStartupProfilingProvider"
android:authorities="${applicationId}.SentryStartupProfilingProvider"
android:exported="false"
android:initOrder="201"/>

<provider
android:name=".SentryPerformanceProvider"
android:authorities="${applicationId}.SentryPerformanceProvider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.SpanStatus;
import io.sentry.TracesSamplingDecision;
import io.sentry.TransactionContext;
import io.sentry.TransactionOptions;
import io.sentry.android.core.internal.util.ClassUtil;
Expand Down Expand Up @@ -161,7 +162,7 @@ private void startTracing(final @NotNull Activity activity) {

final @Nullable SentryDate appStartTime;
final @Nullable Boolean coldStart;
final TimeSpan appStartTimeSpan =
final @NotNull TimeSpan appStartTimeSpan =
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);

// we only track app start for processes that will show an Activity (full launch).
Expand Down Expand Up @@ -205,21 +206,32 @@ private void startTracing(final @NotNull Activity activity) {

// This will be the start timestamp of the transaction, as well as the ttid/ttfd spans
final @NotNull SentryDate ttidStartTime;
final @Nullable TracesSamplingDecision startupSamplingDecision;

if (!(firstActivityCreated || appStartTime == null || coldStart == null)) {
// The first activity ttid/ttfd spans should start at the app start time
ttidStartTime = appStartTime;
// The app start transaction inherits the sampling decision from the startup profiling,
// then clears it
startupSamplingDecision = AppStartMetrics.getInstance().getStartupSamplingDecision();
AppStartMetrics.getInstance().setStartupSamplingDecision(null);
} else {
// The ttid/ttfd spans should start when the previous activity called its onPause method
ttidStartTime = lastPausedTime;
startupSamplingDecision = null;
}
transactionOptions.setStartTimestamp(ttidStartTime);

// we can only bind to the scope if there's no running transaction
ITransaction transaction =
hub.startTransaction(
new TransactionContext(activityName, TransactionNameSource.COMPONENT, UI_LOAD_OP),
transactionOptions);
new TransactionContext(
activityName,
TransactionNameSource.COMPONENT,
UI_LOAD_OP,
startupSamplingDecision),
transactionOptions,
startupSamplingDecision != null);
setSpanOrigin(transaction);

// in case appStartTime isn't available, we don't create a span for it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.sentry.DeduplicateMultithreadedEventProcessor;
import io.sentry.DefaultTransactionPerformanceCollector;
import io.sentry.ILogger;
import io.sentry.ITransactionProfiler;
import io.sentry.NoOpConnectionStatusProvider;
import io.sentry.SendFireAndForgetEnvelopeSender;
import io.sentry.SendFireAndForgetOutboxSender;
Expand All @@ -19,6 +20,7 @@
import io.sentry.android.core.internal.util.AndroidConnectionStatusProvider;
import io.sentry.android.core.internal.util.AndroidMainThreadChecker;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.timber.SentryTimberIntegration;
import io.sentry.cache.PersistingOptionsObserver;
Expand All @@ -34,6 +36,7 @@
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

/**
Expand Down Expand Up @@ -101,7 +104,7 @@ static void loadDefaultAndMetadataOptions(
options.setFlushTimeoutMillis(DEFAULT_FLUSH_TIMEOUT_MS);

ManifestMetadataReader.applyMetadata(context, options, buildInfoProvider);
initializeCacheDirs(context, options);
options.setCacheDirPath(getCacheDir(context).getAbsolutePath());

readDefaultOptionValues(options, context, buildInfoProvider);
}
Expand Down Expand Up @@ -145,10 +148,23 @@ static void initializeIntegrationsAndProcessors(
options.addEventProcessor(new ViewHierarchyEventProcessor(options));
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
options.setTransportGate(new AndroidTransportGate(options));
final SentryFrameMetricsCollector frameMetricsCollector =
new SentryFrameMetricsCollector(context, options, buildInfoProvider);
options.setTransactionProfiler(
new AndroidTransactionProfiler(context, options, buildInfoProvider, frameMetricsCollector));

// Check if the profiler was already instantiated in the startup.
// 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 @Nullable ITransactionProfiler startupProfiler =
AppStartMetrics.getInstance().getStartupProfiler();
if (startupProfiler != null) {
stefanosiano marked this conversation as resolved.
Show resolved Hide resolved
options.setTransactionProfiler(startupProfiler);
AppStartMetrics.getInstance().setStartupProfiler(null);
} else {
final SentryFrameMetricsCollector frameMetricsCollector =
new SentryFrameMetricsCollector(context, options, buildInfoProvider);
options.setTransactionProfiler(
new AndroidTransactionProfiler(
context, options, buildInfoProvider, frameMetricsCollector));
}

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

Expand Down Expand Up @@ -319,14 +335,11 @@ private static void readDefaultOptionValues(
}

/**
* Sets the cache dirs like sentry, outbox and sessions
* Retrieve the Sentry cache dir.
*
* @param context the Application context
* @param options the SentryAndroidOptions
*/
private static void initializeCacheDirs(
final @NotNull Context context, final @NotNull SentryAndroidOptions options) {
final File cacheDir = new File(context.getCacheDir(), "sentry");
options.setCacheDirPath(cacheDir.getAbsolutePath());
static @NotNull File getCacheDir(final @NotNull Context context) {
return new File(context.getCacheDir(), "sentry");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ public synchronized void bindTransaction(final @NotNull ITransaction transaction
endData.measurementsMap);
}

@Override
public boolean isRunning() {
return transactionsCounter != 0;
}

@Override
public void close() {
// we stop profiling
Expand All @@ -307,6 +312,10 @@ public void close() {
true,
null,
HubAdapter.getInstance().getOptions());
} else {
stefanosiano marked this conversation as resolved.
Show resolved Hide resolved
// in case the startup profiling is running, and it's not bound to a transaction, we still
// stop profiling, but we also have to manually update the counter.
transactionsCounter--;
}

// we have to first stop profiling otherwise we would lost the last profile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ final class ManifestMetadataReader {

static final String ENABLE_PERFORMANCE_V2 = "io.sentry.performance-v2.enable";

static final String ENABLE_STARTUP_PROFILING = "io.sentry.profiling.enable-startup";

/** ManifestMetadataReader ctor */
private ManifestMetadataReader() {}

Expand Down Expand Up @@ -365,6 +367,10 @@ static void applyMetadata(

options.setEnablePerformanceV2(
readBool(metadata, logger, ENABLE_PERFORMANCE_V2, options.isEnablePerformanceV2()));

options.setEnableStartupProfiling(
readBool(
metadata, logger, ENABLE_STARTUP_PROFILING, options.isEnableStartupProfiling()));
}

options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,6 @@ public final class SentryAndroidOptions extends SentryOptions {
*/
private boolean enableActivityLifecycleTracingAutoFinish = true;

/**
* Profiling traces rate. 101 hz means 101 traces in 1 second. Defaults to 101 to avoid possible
* lockstep sampling. More on
* https://stackoverflow.com/questions/45470758/what-is-lockstep-sampling
*/
private int profilingTracesHz = 101;

/** Interface that loads the debug images list */
private @NotNull IDebugImagesLoader debugImagesLoader = NoOpDebugImagesLoader.getInstance();

Expand Down Expand Up @@ -362,22 +355,6 @@ public int getProfilingTracesIntervalMillis() {
@Deprecated
public void setProfilingTracesIntervalMillis(final int profilingTracesIntervalMillis) {}

/**
* Returns the rate the profiler will sample rates at. 100 hz means 100 traces in 1 second.
*
* @return Rate the profiler will sample rates at.
*/
@ApiStatus.Internal
public int getProfilingTracesHz() {
return profilingTracesHz;
}

/** Sets the rate the profiler will sample rates at. 100 hz means 100 traces in 1 second. */
@ApiStatus.Internal
public void setProfilingTracesHz(final int profilingTracesHz) {
this.profilingTracesHz = profilingTracesHz;
}

/**
* Returns the Debug image loader
*
Expand Down
Loading