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 1 - Decouple Profiler from Transaction #3101

Merged
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Unreleased

### Features

- Added Startup profiling
- This depends on the new option `io.sentry.profiling.enable-startup`, other than the already existing `io.sentry.traces.profiling.sample-rate`.
- Sampler functions can check the new `isForNextStartup` flag, to adjust startup profiling sampling programmatically.
Relevant PRs:
- Decouple Profiler from Transaction ([#3101](https://github.com/getsentry/sentry-java/pull/3101))
- Add options and sampling logic ([#3121](https://github.com/getsentry/sentry-java/pull/3121))
- Add ContentProvider and start profile ([#3128](https://github.com/getsentry/sentry-java/pull/3128))

## 7.2.0

### Features
Expand Down
7 changes: 5 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 @@ -345,6 +343,7 @@ public final class io/sentry/android/core/SentryPerformanceProvider {
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 {
Expand Down Expand Up @@ -426,12 +425,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
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,20 +206,31 @@ 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);
transactionOptions.setStartupTransaction(startupSamplingDecision != null);

// 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),
new TransactionContext(
activityName,
TransactionNameSource.COMPONENT,
UI_LOAD_OP,
startupSamplingDecision),
transactionOptions);
setSpanOrigin(transaction);

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,25 @@ 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.
synchronized (AppStartMetrics.getInstance()) {
final @Nullable ITransactionProfiler startupProfiler =
AppStartMetrics.getInstance().getStartupProfiler();
if (startupProfiler != null) {
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 +337,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 @@ -73,7 +73,7 @@ public ProfileEndData(
private static final int BUFFER_SIZE_BYTES = 3_000_000;

private static final int PROFILING_TIMEOUT_MILLIS = 30_000;
private long transactionStartNanos = 0;
private long profileStartNanos = 0;
private final @NotNull File traceFilesDir;
private final int intervalUs;
private @Nullable Future<?> scheduledFinish = null;
Expand Down Expand Up @@ -150,14 +150,14 @@ public void onFrameMetricCollected(
final boolean isSlow,
final boolean isFrozen,
final float refreshRate) {
// transactionStartNanos is calculated through SystemClock.elapsedRealtimeNanos(),
// profileStartNanos is calculated through SystemClock.elapsedRealtimeNanos(),
// but frameEndNanos uses System.nanotime(), so we convert it to get the timestamp
// relative to transactionStartNanos
// relative to profileStartNanos
final long frameTimestampRelativeNanos =
frameEndNanos
- System.nanoTime()
+ SystemClock.elapsedRealtimeNanos()
- transactionStartNanos;
- profileStartNanos;

// We don't allow negative relative timestamps.
// So we add a check, even if this should never happen.
Expand Down Expand Up @@ -191,7 +191,7 @@ public void onFrameMetricCollected(
e);
}

transactionStartNanos = SystemClock.elapsedRealtimeNanos();
profileStartNanos = SystemClock.elapsedRealtimeNanos();
long profileStartCpuMillis = Process.getElapsedCpuTime();

// We don't make any check on the file existence or writeable state, because we don't want to
Expand All @@ -203,7 +203,7 @@ public void onFrameMetricCollected(
// tests)
Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs);
isRunning = true;
return new ProfileStartData(transactionStartNanos, profileStartCpuMillis);
return new ProfileStartData(profileStartNanos, profileStartCpuMillis);
} catch (Throwable e) {
endAndCollect(false, null);
logger.log(SentryLevel.ERROR, "Unable to start a profile: ", e);
Expand Down Expand Up @@ -304,7 +304,7 @@ private void putPerformanceCollectionDataInMeasurements(
// the beginning, expressed with SystemClock.elapsedRealtimeNanos()
long timestampDiff =
SystemClock.elapsedRealtimeNanos()
- transactionStartNanos
- profileStartNanos
- TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis());
if (performanceCollectionData != null) {
final @NotNull ArrayDeque<ProfileMeasurementValue> memoryUsageMeasurements =
Expand Down
Loading