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

Mark previous session as abnormal if an ANR (ANRv2) has happened #2621

Merged
merged 19 commits into from
Apr 4, 2023
Merged
9 changes: 5 additions & 4 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ public class io/sentry/android/core/AnrV2Integration : io/sentry/Integration, ja
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/Backfillable {
public fun <init> (JLio/sentry/ILogger;JZ)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 ()J
public fun timestamp ()Ljava/lang/Long;
}

public final class io/sentry/android/core/AppComponentsBreadcrumbsIntegration : android/content/ComponentCallbacks2, io/sentry/Integration, java/io/Closeable {
Expand Down Expand Up @@ -323,7 +324,7 @@ public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry
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;)J
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 @@ -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
Expand Up @@ -302,7 +302,7 @@ private void setApp(final @NotNull SentryBaseEvent event) {
} catch (Throwable e) {
options
.getLogger()
.log(SentryLevel.ERROR, "Failed to parse release from scope cache: %s", release);
.log(SentryLevel.WARNING, "Failed to parse release from scope cache: %s", release);
}
}

Expand Down Expand Up @@ -363,7 +363,7 @@ private void setDist(final @NotNull SentryBaseEvent event) {
} catch (Throwable e) {
options
.getLogger()
.log(SentryLevel.ERROR, "Failed to parse release from scope cache: %s", release);
.log(SentryLevel.WARNING, "Failed to parse release from scope cache: %s", release);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.android.core.cache.AndroidEnvelopeCache;
import io.sentry.cache.EnvelopeCache;
import io.sentry.cache.IEnvelopeCache;
import io.sentry.exception.ExceptionMechanismException;
import io.sentry.hints.AbnormalExit;
import io.sentry.hints.Backfillable;
import io.sentry.hints.BlockingFlushHint;
import io.sentry.protocol.Mechanism;
Expand Down Expand Up @@ -75,26 +78,9 @@ public void register(@NotNull IHub hub, @NotNull SentryOptions options) {
}

if (this.options.isAnrEnabled()) {
final ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

List<ApplicationExitInfo> applicationExitInfoList =
activityManager.getHistoricalProcessExitReasons(null, 0, 0);

if (applicationExitInfoList.size() != 0) {
options
.getExecutorService()
.submit(
new AnrProcessor(
new ArrayList<>(
applicationExitInfoList), // just making a deep copy to be safe, as we're
// modifying the list
hub,
this.options,
dateProvider));
} else {
options.getLogger().log(SentryLevel.DEBUG, "No records in historical exit reasons.");
}
options
.getExecutorService()
.submit(new AnrProcessor(context, hub, this.options, dateProvider));
options.getLogger().log(SentryLevel.DEBUG, "AnrV2Integration installed.");
addIntegrationToSdkVersion();
}
Expand All @@ -109,17 +95,17 @@ public void close() throws IOException {

static class AnrProcessor implements Runnable {

final @NotNull List<ApplicationExitInfo> exitInfos;
private final @NotNull Context context;
private final @NotNull IHub hub;
private final @NotNull SentryAndroidOptions options;
private final long threshold;

AnrProcessor(
final @NotNull List<ApplicationExitInfo> exitInfos,
final @NotNull Context context,
final @NotNull IHub hub,
final @NotNull SentryAndroidOptions options,
final @NotNull ICurrentDateProvider dateProvider) {
this.exitInfos = exitInfos;
this.context = context;
this.hub = hub;
this.options = options;
this.threshold = dateProvider.getCurrentTimeMillis() - NINETY_DAYS_THRESHOLD;
Expand All @@ -128,7 +114,37 @@ static class AnrProcessor implements Runnable {
@SuppressLint("NewApi") // we check this in AnrIntegrationFactory
@Override
public void run() {
final long lastReportedAnrTimestamp = AndroidEnvelopeCache.lastReportedAnr(options);
final ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

final List<ApplicationExitInfo> applicationExitInfoList =
activityManager.getHistoricalProcessExitReasons(null, 0, 0);
if (applicationExitInfoList.size() == 0) {
options.getLogger().log(SentryLevel.DEBUG, "No records in historical exit reasons.");
return;
}

final IEnvelopeCache cache = options.getEnvelopeDiskCache();
if (cache instanceof EnvelopeCache) {
if (options.isEnableAutoSessionTracking()
&& !((EnvelopeCache) cache).waitPreviousSessionFlush()) {
options
.getLogger()
.log(
SentryLevel.WARNING,
"Timed out waiting to flush previous session to its own file.");

// if we timed out waiting here, we can already flush the latch, because the timeout is
// big
// enough to wait for it only once and we don't have to wait again in
// PreviousSessionFinalizer
((EnvelopeCache) cache).flushPreviousSession();
}
}

// making a deep copy as we're modifying the list
final List<ApplicationExitInfo> exitInfos = new ArrayList<>(applicationExitInfoList);
final @Nullable Long lastReportedAnrTimestamp = AndroidEnvelopeCache.lastReportedAnr(options);

// search for the latest ANR to report it separately as we're gonna enrich it. The latest
// ANR will be first in the list, as it's filled last-to-first in order of appearance
Expand Down Expand Up @@ -156,7 +172,8 @@ public void run() {
return;
}

if (latestAnr.getTimestamp() <= lastReportedAnrTimestamp) {
if (lastReportedAnrTimestamp != null
&& latestAnr.getTimestamp() <= lastReportedAnrTimestamp) {
options
.getLogger()
.log(SentryLevel.DEBUG, "Latest ANR has already been reported, returning early.");
Expand All @@ -172,7 +189,7 @@ public void run() {
}

private void reportNonEnrichedHistoricalAnrs(
final @NotNull List<ApplicationExitInfo> exitInfos, final long lastReportedAnr) {
final @NotNull List<ApplicationExitInfo> exitInfos, final @Nullable Long lastReportedAnr) {
// we reverse the list, because the OS puts errors in order of appearance, last-to-first
// and we want to write a marker file after each ANR has been processed, so in case the app
// gets killed meanwhile, we can proceed from the last reported ANR and not process the entire
Expand All @@ -187,7 +204,7 @@ private void reportNonEnrichedHistoricalAnrs(
continue;
}

if (applicationExitInfo.getTimestamp() <= lastReportedAnr) {
if (lastReportedAnr != null && applicationExitInfo.getTimestamp() <= lastReportedAnr) {
options
.getLogger()
.log(SentryLevel.DEBUG, "ANR has already been reported %s.", applicationExitInfo);
Expand All @@ -202,10 +219,16 @@ private void reportNonEnrichedHistoricalAnrs(
private void reportAsSentryEvent(
final @NotNull ApplicationExitInfo exitInfo, final boolean shouldEnrich) {
final long anrTimestamp = exitInfo.getTimestamp();
final Throwable anrThrowable = buildAnrThrowable(exitInfo);
final boolean isBackground =
exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
final Throwable anrThrowable = buildAnrThrowable(exitInfo, isBackground);
final AnrV2Hint anrHint =
new AnrV2Hint(
options.getFlushTimeoutMillis(), options.getLogger(), anrTimestamp, shouldEnrich);
options.getFlushTimeoutMillis(),
options.getLogger(),
anrTimestamp,
shouldEnrich,
isBackground);

final Hint hint = HintUtils.createWithTypeCheckHint(anrHint);

Expand All @@ -228,10 +251,8 @@ private void reportAsSentryEvent(
}
}

private @NotNull Throwable buildAnrThrowable(final @NotNull ApplicationExitInfo exitInfo) {
final boolean isBackground =
exitInfo.getImportance() != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;

private @NotNull Throwable buildAnrThrowable(
final @NotNull ApplicationExitInfo exitInfo, final boolean isBackground) {
String message = "ANR";
if (isBackground) {
message = "Background " + message;
Expand All @@ -248,29 +269,40 @@ private void reportAsSentryEvent(
}

@ApiStatus.Internal
public static final class AnrV2Hint extends BlockingFlushHint implements Backfillable {
public static final class AnrV2Hint extends BlockingFlushHint
implements Backfillable, AbnormalExit {
romtsn marked this conversation as resolved.
Show resolved Hide resolved

private final long timestamp;

private final boolean shouldEnrich;

private final boolean isBackgroundAnr;

public AnrV2Hint(
final long flushTimeoutMillis,
final @NotNull ILogger logger,
final long timestamp,
final boolean shouldEnrich) {
final boolean shouldEnrich,
final boolean isBackgroundAnr) {
super(flushTimeoutMillis, logger);
this.timestamp = timestamp;
this.shouldEnrich = shouldEnrich;
this.isBackgroundAnr = isBackgroundAnr;
}

public long timestamp() {
@Override
public Long timestamp() {
return timestamp;
}

@Override
public boolean shouldEnrich() {
return shouldEnrich;
}

@Override
public String mechanism() {
return isBackgroundAnr ? "anr_background" : "anr_foreground";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.OutputStream;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

@ApiStatus.Internal
Expand Down Expand Up @@ -70,15 +71,15 @@ public void store(@NotNull SentryEnvelope envelope, @NotNull Hint hint) {
hint,
AnrV2Integration.AnrV2Hint.class,
(anrHint) -> {
final long timestamp = anrHint.timestamp();
final @Nullable Long timestamp = anrHint.timestamp();
options
.getLogger()
.log(
SentryLevel.DEBUG,
"Writing last reported ANR marker with timestamp %d",
timestamp);

writeLastReportedAnrMarker(anrHint.timestamp());
writeLastReportedAnrMarker(timestamp);
});
}

Expand Down Expand Up @@ -136,7 +137,7 @@ public static boolean hasStartupCrashMarker(final @NotNull SentryOptions options
return false;
}

public static long lastReportedAnr(final @NotNull SentryOptions options) {
public static @Nullable Long lastReportedAnr(final @NotNull SentryOptions options) {
final String cacheDirPath =
Objects.requireNonNull(
options.getCacheDirPath(), "Cache dir path should be set for getting ANRs reported");
Expand All @@ -147,7 +148,7 @@ public static long lastReportedAnr(final @NotNull SentryOptions options) {
final String content = FileUtils.readText(lastAnrMarker);
// we wrapped into try-catch already
//noinspection ConstantConditions
return Long.parseLong(content.trim());
return content.equals("null") ? null : Long.parseLong(content.trim());
} else {
options
.getLogger()
Expand All @@ -156,10 +157,10 @@ public static long lastReportedAnr(final @NotNull SentryOptions options) {
} catch (Throwable e) {
options.getLogger().log(ERROR, "Error reading last ANR marker", e);
}
return 0L;
return null;
}

private void writeLastReportedAnrMarker(final long timestamp) {
private void writeLastReportedAnrMarker(final @Nullable Long timestamp) {
final String cacheDirPath = options.getCacheDirPath();
if (cacheDirPath == null) {
options.getLogger().log(DEBUG, "Cache dir path is null, the ANR marker will not be written");
Expand Down
Loading