Skip to content

Commit

Permalink
Merge branch 'main' into feat/public-transaction-methods
Browse files Browse the repository at this point in the history
  • Loading branch information
lbloder committed May 5, 2023
2 parents 460eff1 + 2011dda commit f9cc298
Show file tree
Hide file tree
Showing 104 changed files with 7,326 additions and 667 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,22 @@

## Unreleased

### Features

- Add Screenshot and ViewHierarchy to integrations list ([#2698](https://github.com/getsentry/sentry-java/pull/2698))
- New ANR detection based on [ApplicationExitInfo API](https://developer.android.com/reference/android/app/ApplicationExitInfo) ([#2697](https://github.com/getsentry/sentry-java/pull/2697))
- This implementation completely replaces the old one (based on a watchdog) on devices running Android 11 and above:
- New implementation provides more precise ANR events/ANR rate detection as well as system thread dump information. The new implementation reports ANRs exactly as Google Play Console, without producing false positives or missing important background ANR events.
- However, despite producing many false positives, the old implementation is capable of better enriching ANR errors (which is not available with the new implementation), for example:
- Capturing screenshots at the time of ANR event;
- Capturing transactions and profiling data corresponding to the ANR event;
- Auxiliary information (such as current memory load) at the time of ANR event.
- If you would like us to provide support for the old approach working alongside the new one on Android 11 and above (e.g. for raising events for slow code on main thread), consider upvoting [this issue](https://github.com/getsentry/sentry-java/issues/2693).
- The old watchdog implementation will continue working for older API versions (Android < 11)

### Fixes

- Android Profiler on calling thread ([#2691](https://github.com/getsentry/sentry-java/pull/2691))
- Use `configureScope` instead of `withScope` in `Hub.close()`. This ensures that the main scope releases the in-memory data when closing a hub instance. ([#2688](https://github.com/getsentry/sentry-java/pull/2688))

### Dependencies
Expand Down
2 changes: 2 additions & 0 deletions sentry-android-core/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
INSTALLATION
last_crash
.options-cache/
.scope-cache/
/sentry
29 changes: 27 additions & 2 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@ public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration
public final fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/AnrIntegrationFactory {
public fun <init> ()V
public static fun create (Landroid/content/Context;Lio/sentry/android/core/BuildInfoProvider;)Lio/sentry/Integration;
}

public final class io/sentry/android/core/AnrV2EventProcessor : io/sentry/BackfillingEventProcessor {
public fun <init> (Landroid/content/Context;Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
}

public class io/sentry/android/core/AnrV2Integration : io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun close ()V
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/AnrV2Integration$AnrV2Hint : io/sentry/hints/BlockingFlushHint, io/sentry/hints/AbnormalExit, io/sentry/hints/Backfillable {
public fun <init> (JLio/sentry/ILogger;JZZ)V
public fun mechanism ()Ljava/lang/String;
public fun shouldEnrich ()Z
public fun timestamp ()Ljava/lang/Long;
}

public final class io/sentry/android/core/AppComponentsBreadcrumbsIntegration : android/content/ComponentCallbacks2, io/sentry/Integration, java/io/Closeable {
public fun <init> (Landroid/content/Context;)V
public fun close ()V
Expand Down Expand Up @@ -163,7 +186,7 @@ public final class io/sentry/android/core/PhoneStateBreadcrumbsIntegration : io/
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/ScreenshotEventProcessor : io/sentry/EventProcessor {
public final class io/sentry/android/core/ScreenshotEventProcessor : io/sentry/EventProcessor, io/sentry/IntegrationName {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/BuildInfoProvider;)V
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
}
Expand Down Expand Up @@ -288,7 +311,7 @@ public final class io/sentry/android/core/UserInteractionIntegration : android/a
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
}

public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentry/EventProcessor {
public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentry/EventProcessor, io/sentry/IntegrationName {
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
public static fun snapshotViewHierarchy (Landroid/app/Activity;Lio/sentry/ILogger;)Lio/sentry/protocol/ViewHierarchy;
Expand All @@ -297,9 +320,11 @@ public final class io/sentry/android/core/ViewHierarchyEventProcessor : io/sentr
}

public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry/cache/EnvelopeCache {
public static final field LAST_ANR_REPORT Ljava/lang/String;
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;)V
public fun getDirectory ()Ljava/io/File;
public static fun hasStartupCrashMarker (Lio/sentry/SentryOptions;)Z
public static fun lastReportedAnr (Lio/sentry/SentryOptions;)Ljava/lang/Long;
public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
}

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.fragment.FragmentLifecycleIntegration;
import io.sentry.android.timber.SentryTimberIntegration;
import io.sentry.cache.PersistingOptionsObserver;
import io.sentry.cache.PersistingScopeObserver;
import io.sentry.compose.gestures.ComposeGestureTargetLocator;
import io.sentry.internal.gestures.GestureTargetLocator;
import io.sentry.transport.NoOpEnvelopeCache;
Expand Down Expand Up @@ -139,6 +141,7 @@ static void initializeIntegrationsAndProcessors(
options.addEventProcessor(new PerformanceAndroidEventProcessor(options, activityFramesTracker));
options.addEventProcessor(new ScreenshotEventProcessor(options, buildInfoProvider));
options.addEventProcessor(new ViewHierarchyEventProcessor(options));
options.addEventProcessor(new AnrV2EventProcessor(context, options, buildInfoProvider));
options.setTransportGate(new AndroidTransportGate(context, options.getLogger()));
final SentryFrameMetricsCollector frameMetricsCollector =
new SentryFrameMetricsCollector(context, options, buildInfoProvider);
Expand Down Expand Up @@ -170,6 +173,11 @@ static void initializeIntegrationsAndProcessors(
options.addCollector(new AndroidCpuCollector(options.getLogger(), buildInfoProvider));
}
options.setTransactionPerformanceCollector(new DefaultTransactionPerformanceCollector(options));

if (options.getCacheDirPath() != null) {
options.addScopeObserver(new PersistingScopeObserver(options));
options.addOptionsObserver(new PersistingOptionsObserver(options));
}
}

private static void installDefaultIntegrations(
Expand Down Expand Up @@ -213,7 +221,7 @@ private static void installDefaultIntegrations(
// AppLifecycleIntegration has to be installed before AnrIntegration, because AnrIntegration
// relies on AppState set by it
options.addIntegration(new AppLifecycleIntegration());
options.addIntegration(new AnrIntegration(context));
options.addIntegration(AnrIntegrationFactory.create(context, buildInfoProvider));

// registerActivityLifecycleCallbacks is only available if Context is an AppContext
if (context instanceof Application) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -144,21 +143,6 @@ private void init() {

@Override
public synchronized void onTransactionStart(final @NotNull ITransaction transaction) {
try {
options.getExecutorService().submit(() -> onTransactionStartSafe(transaction));
} catch (RejectedExecutionException e) {
options
.getLogger()
.log(
SentryLevel.ERROR,
"Failed to call the executor. Profiling will not be started. Did you call Sentry.close()?",
e);
}
}

@SuppressLint("NewApi")
private void onTransactionStartSafe(final @NotNull ITransaction transaction) {

// Debug.startMethodTracingSampling() is only available since Lollipop
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP) return;

Expand All @@ -167,21 +151,22 @@ private void onTransactionStartSafe(final @NotNull ITransaction transaction) {

// traceFilesDir is null or intervalUs is 0 only if there was a problem in the init, but
// we already logged that
if (traceFilesDir == null || intervalUs == 0 || !traceFilesDir.canWrite()) {
if (traceFilesDir == null || intervalUs == 0) {
return;
}

transactionsCounter++;
// When the first transaction is starting, we can start profiling
if (transactionsCounter == 1) {
onFirstTransactionStarted(transaction);
options
.getLogger()
.log(
SentryLevel.DEBUG,
"Transaction %s (%s) started and being profiled.",
transaction.getName(),
transaction.getSpanContext().getTraceId().toString());
if (onFirstTransactionStarted(transaction)) {
options
.getLogger()
.log(
SentryLevel.DEBUG,
"Transaction %s (%s) started and being profiled.",
transaction.getName(),
transaction.getSpanContext().getTraceId().toString());
}
} else {
transactionsCounter--;
options
Expand All @@ -195,7 +180,7 @@ private void onTransactionStartSafe(final @NotNull ITransaction transaction) {
}

@SuppressLint("NewApi")
private void onFirstTransactionStarted(final @NotNull ITransaction transaction) {
private boolean onFirstTransactionStarted(final @NotNull ITransaction transaction) {
// We create a file with a uuid name, so no need to check if it already exists
traceFile = new File(traceFilesDir, UUID.randomUUID() + ".trace");

Expand Down Expand Up @@ -273,33 +258,28 @@ public void onFrameMetricCollected(
currentProfilingTransactionData =
new ProfilingTransactionData(transaction, transactionStartNanos, profileStartCpuMillis);

Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs);
// We don't make any check on the file existence or writeable state, because we don't want to
// make file IO in the main thread.
// We cannot offload the work to the executorService, as if that's very busy, profiles could
// start/stop with a lot of delay and even cause ANRs.
try {
// If there is any problem with the file this method will throw (but it will not throw in
// tests)
Debug.startMethodTracingSampling(traceFile.getPath(), BUFFER_SIZE_BYTES, intervalUs);
return true;
} catch (Throwable e) {
onTransactionFinish(transaction, null);
options.getLogger().log(SentryLevel.ERROR, "Unable to start a profile: ", e);
return false;
}
}

@Override
public @Nullable synchronized ProfilingTraceData onTransactionFinish(
final @NotNull ITransaction transaction,
final @Nullable List<PerformanceCollectionData> performanceCollectionData) {
try {
return options
.getExecutorService()
.submit(() -> onTransactionFinish(transaction, false, performanceCollectionData))
.get();
} catch (ExecutionException | InterruptedException e) {
options.getLogger().log(SentryLevel.ERROR, "Error finishing profiling: ", e);
// We stop profiling even on exceptions, so profiles don't run indefinitely
onTransactionFinish(transaction, false, null);
} catch (RejectedExecutionException e) {
options
.getLogger()
.log(
SentryLevel.ERROR,
"Failed to call the executor. Profiling could not be finished. Did you call Sentry.close()?",
e);
// We stop profiling even on exceptions, so profiles don't run indefinitely
onTransactionFinish(transaction, false, null);
}
return null;

return onTransactionFinish(transaction, false, performanceCollectionData);
}

@SuppressLint("NewApi")
Expand Down Expand Up @@ -370,7 +350,14 @@ public void onFrameMetricCollected(
return null;
}

Debug.stopMethodTracing();
try {
// If there is any problem with the file this method could throw, but the start is also
// wrapped, so this should never happen (except for tests, where this is the only method that
// throws)
Debug.stopMethodTracing();
} catch (Throwable e) {
options.getLogger().log(SentryLevel.ERROR, "Error while stopping profiling: ", e);
}
frameMetricsCollector.stopCollection(frameMetricsCollectorId);

long transactionEndNanos = SystemClock.elapsedRealtimeNanos();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.sentry.SentryOptions;
import io.sentry.exception.ExceptionMechanismException;
import io.sentry.hints.AbnormalExit;
import io.sentry.hints.TransactionEnd;
import io.sentry.protocol.Mechanism;
import io.sentry.util.HintUtils;
import io.sentry.util.Objects;
Expand Down Expand Up @@ -141,7 +142,7 @@ public void close() throws IOException {
* href="https://develop.sentry.dev/sdk/sessions/#crashed-abnormal-vs-errored">Develop Docs</a>
* because we don't know whether the app has recovered after it or not.
*/
static final class AnrHint implements AbnormalExit {
static final class AnrHint implements AbnormalExit, TransactionEnd {

private final boolean isBackgroundAnr;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.sentry.android.core;

import android.content.Context;
import android.os.Build;
import io.sentry.Integration;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

@ApiStatus.Internal
public final class AnrIntegrationFactory {

@NotNull
public static Integration create(
final @NotNull Context context, final @NotNull BuildInfoProvider buildInfoProvider) {
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.R) {
return new AnrV2Integration(context);
} else {
return new AnrIntegration(context);
}
}
}
Loading

0 comments on commit f9cc298

Please sign in to comment.