Skip to content

Commit

Permalink
added API for sending ANR manually
Browse files Browse the repository at this point in the history
commit_hash:d0528c40499d287e3c230b24282c6e1c177aeef9
  • Loading branch information
alexklints committed Nov 1, 2024
1 parent ae0626e commit ec5e1f0
Show file tree
Hide file tree
Showing 27 changed files with 347 additions and 33 deletions.
2 changes: 2 additions & 0 deletions .mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@
"analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/ANRMonitor.java":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/ANRMonitor.java",
"analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AllThreads.java":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AllThreads.java",
"analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/Anr.java":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/Anr.java",
"analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrFromApiReportingTask.kt":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrFromApiReportingTask.kt",
"analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrReporter.kt":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrReporter.kt",
"analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AppMetricaUncaughtExceptionHandler.kt":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/AppMetricaUncaughtExceptionHandler.kt",
"analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/CrashProcessor.java":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/main/java/io/appmetrica/analytics/impl/crash/jvm/client/CrashProcessor.java",
Expand Down Expand Up @@ -1496,6 +1497,7 @@
"analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/JvmCrashTest.java":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/JvmCrashTest.java",
"analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/ANRMonitorTest.kt":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/ANRMonitorTest.kt",
"analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AllThreadsTest.java":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AllThreadsTest.java",
"analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrFromApiReportingTaskTest.kt":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrFromApiReportingTaskTest.kt",
"analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrTest.java":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AnrTest.java",
"analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AppMetricaUncaughtExceptionHandlerTest.kt":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/AppMetricaUncaughtExceptionHandlerTest.kt",
"analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/CrashProcessorCompositeTest.kt":"mobile/metrika/android/appmetrica/appmetrica-sdk/analytics/src/test/java/io/appmetrica/analytics/impl/crash/jvm/client/CrashProcessorCompositeTest.kt",
Expand Down
2 changes: 2 additions & 0 deletions analytics/api/prodRelease.api
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public final class io.appmetrica.analytics.AppMetrica {
public static void putErrorEnvironmentValue([NonNull] String, [Nullable] String)
public static void registerAnrListener([NonNull] io.appmetrica.analytics.AnrListener)
public static void reportAdRevenue([NonNull] io.appmetrica.analytics.AdRevenue)
public static void reportAnr([NonNull] java.util.Map<Thread, StackTraceElement[]>)
public static void reportAppOpen([NonNull] android.app.Activity)
public static void reportAppOpen([NonNull] android.content.Intent)
public static void reportAppOpen([NonNull] String)
Expand Down Expand Up @@ -270,6 +271,7 @@ public interface io.appmetrica.analytics.IReporter {
public abstract void pauseSession()
public abstract void putAppEnvironmentValue([NonNull] String, [Nullable] String)
public abstract void reportAdRevenue([NonNull] io.appmetrica.analytics.AdRevenue)
public abstract void reportAnr([NonNull] java.util.Map<Thread, StackTraceElement[]>)
public abstract void reportECommerce([NonNull] io.appmetrica.analytics.ecommerce.ECommerceEvent)
public abstract void reportError([NonNull] String, [Nullable] String)
public abstract void reportError([NonNull] String, [Nullable] String, [Nullable] Throwable)
Expand Down
16 changes: 16 additions & 0 deletions analytics/src/main/java/io/appmetrica/analytics/AppMetrica.java
Original file line number Diff line number Diff line change
Expand Up @@ -622,4 +622,20 @@ public static void reportExternalAttribution(@NonNull ExternalAttribution value)
public static void reportExternalAdRevenue(@NonNull Object... values) {
AppMetricaProxyProvider.getProxy().reportExternalAdRevenue(values);
}

/**
* Sends an ANR event (the application is not responding) manually.
* It is an alternative for automatic ANR tracking, which is enabled in the ${@link AppMetricaConfig} configuration
* during SDK activation.
*
* @param allThreads A snapshot of all streams with stack traces.
* Can be received by calling {@link Thread#getAllStackTraces()}.
*
* @see AppMetricaConfig.Builder#withAnrMonitoring(boolean)
* @see AppMetricaConfig.Builder#withAnrMonitoringTimeout(int)
* @see AppMetrica#activate(Context, AppMetricaConfig)
*/
public static void reportAnr(@NonNull Map<Thread, StackTraceElement[]> allThreads) {
AppMetricaProxyProvider.getProxy().reportAnr(allThreads);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,12 @@ public interface IReporter {
* <p>If called before metrica initialization, app environment will be cleared right after init
*/
void clearAppEnvironment();

/**
* Sends an ANR event (the application is not responding) manually.
*
* @param allThreads A snapshot of all streams with stack traces.
* Can be received by calling {@link Thread#getAllStackTraces()}.
*/
void reportAnr(@NonNull Map<Thread, StackTraceElement[]> allThreads);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.appmetrica.analytics.impl.crash.PluginErrorDetailsConverter;
import io.appmetrica.analytics.impl.crash.jvm.client.AllThreads;
import io.appmetrica.analytics.impl.crash.jvm.client.Anr;
import io.appmetrica.analytics.impl.crash.jvm.client.AnrFromApiReportingTask;
import io.appmetrica.analytics.impl.crash.jvm.client.CustomError;
import io.appmetrica.analytics.impl.crash.jvm.client.RegularError;
import io.appmetrica.analytics.impl.crash.jvm.client.UnhandledException;
Expand Down Expand Up @@ -517,6 +518,11 @@ public void setDataSendingEnabled(boolean value) {
mPublicLogger.info("Updated data sending enabled: %s", value);
}

@Override
public void reportAnr(@NonNull Map<Thread, StackTraceElement[]> allThreads) {
new AnrFromApiReportingTask(this, allThreads).execute();
}

@Override
public void reportAnr(@NonNull AllThreads allThreads) {
Anr anr = new Anr(allThreads, mExtraMetaInfoRetriever.getBuildId(), mExtraMetaInfoRetriever.isOffline());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.appmetrica.analytics.impl.crash.jvm.client

import android.os.Looper
import io.appmetrica.analytics.impl.ClientServiceLocator
import io.appmetrica.analytics.impl.crash.utils.FullStateConverter
import io.appmetrica.analytics.impl.crash.utils.ThreadsStateDumper

class AnrFromApiReportingTask(
private val anrReporter: AnrReporter,
private val allThreads: Map<Thread, Array<StackTraceElement>>
) {

private val mainThread: Thread = Looper.getMainLooper().thread

fun execute() {
val threadsStateDumper = ThreadsStateDumper(
object : ThreadsStateDumper.ThreadProvider {
override fun getMainThread(): Thread = this@AnrFromApiReportingTask.mainThread
override fun getMainThreadStacktrace(): Array<StackTraceElement>? = allThreads[mainThread]
override fun getAllThreadsStacktraces(): Map<Thread, Array<StackTraceElement>> = allThreads
},
FullStateConverter(),
ClientServiceLocator.getInstance().processDetector
)

anrReporter.reportAnr(threadsStateDumper.threadsDumpForAnr)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public class ThreadState {
public final List<StackTraceElement> stacktrace;

public ThreadState(@NonNull String name,
int priority, long tid,
int priority,
long tid,
@NonNull String group,
@Nullable Integer state,
@Nullable List<StackTraceElement> stacktrace) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.appmetrica.analytics.coreapi.internal.backport.BiFunction;
import io.appmetrica.analytics.coreutils.internal.StringUtils;
import io.appmetrica.analytics.impl.ClientServiceLocator;
Expand All @@ -18,11 +17,16 @@

public class ThreadsStateDumper {

interface ThreadProvider {
public interface ThreadProvider {

@NonNull
Thread getMainThread();

Map<Thread, StackTraceElement[]> getAllOtherThreads();
@Nullable
StackTraceElement[] getMainThreadStacktrace();

@NonNull
Map<Thread, StackTraceElement[]> getAllThreadsStacktraces();

}

Expand All @@ -32,24 +36,34 @@ interface ThreadProvider {

public ThreadsStateDumper() {
this(new ThreadProvider() {
@Override
public Thread getMainThread() {
return Looper.getMainLooper().getThread();
}

@Override
public Map<Thread, StackTraceElement[]> getAllOtherThreads() {
return Thread.getAllStackTraces();
}
}, new FullStateConverter(),
@Override
@NonNull
public Thread getMainThread() {
return Looper.getMainLooper().getThread();
}

@Nullable
@Override
public StackTraceElement[] getMainThreadStacktrace() {
return null;
}

@Override
@NonNull
public Map<Thread, StackTraceElement[]> getAllThreadsStacktraces() {
return Thread.getAllStackTraces();
}
}, new FullStateConverter(),
ClientServiceLocator.getInstance().getProcessDetector()
);
}

@VisibleForTesting()
ThreadsStateDumper(@NonNull ThreadProvider threadProvider,
@NonNull BiFunction<Thread, StackTraceElement[], ThreadState> converter,
@NonNull ProcessDetector processDetector) {
public ThreadsStateDumper(
@NonNull ThreadProvider threadProvider,
@NonNull BiFunction<Thread, StackTraceElement[], ThreadState> converter,
@NonNull ProcessDetector processDetector
) {
this.threadProvider = threadProvider;
this.threadConverter = converter;
this.processDetector = processDetector;
Expand All @@ -58,13 +72,12 @@ public Map<Thread, StackTraceElement[]> getAllOtherThreads() {
/**
* @return String dump of all a thread details and stacktraces.
*/
//region changed code
public AllThreads getThreadsDumpForAnr() {
Thread mainThread = threadProvider.getMainThread();
return new AllThreads(
getMainThreadState(mainThread),
getAllThreadsDump(mainThread, null),
processDetector.getProcessName()
getMainThreadState(mainThread),
getAllThreadsDump(mainThread, null),
processDetector.getProcessName()
);
}

Expand All @@ -80,7 +93,10 @@ public List<ThreadState> getThreadsDumpForCrash(@Nullable Thread excludedThread)
private ThreadState getMainThreadState(@NonNull Thread mainThread) {
StackTraceElement[] mainStackTrace = null;
try {
mainStackTrace = mainThread.getStackTrace();
mainStackTrace = threadProvider.getMainThreadStacktrace();
if (mainStackTrace == null) {
mainStackTrace = mainThread.getStackTrace();
}
} catch (SecurityException e) { /* do nothing */ }

return threadConverter.apply(mainThread, mainStackTrace);
Expand All @@ -96,9 +112,7 @@ public int compare(Thread first, Thread second) {
if (first == second) {
return 0;
}
//region changed code
return StringUtils.compare(first.getName(), second.getName());
//endregion
}
};

Expand All @@ -108,18 +122,16 @@ public int compare(Thread first, Thread second) {
Map<Thread, StackTraceElement[]> allStackTraces = null;

try {
allStackTraces = threadProvider.getAllOtherThreads();
allStackTraces = threadProvider.getAllThreadsStacktraces();
} catch (SecurityException e) { /* do nothing */ }

if (allStackTraces != null) {
stackTraces.putAll(allStackTraces);
}

//region changed code
if (excludedThread != null) {
stackTraces.remove(excludedThread);
}
//endregion

for (Map.Entry<Thread, StackTraceElement[]> entry : stackTraces.entrySet()) {
final Thread thread = entry.getKey();
Expand All @@ -129,14 +141,9 @@ public int compare(Thread first, Thread second) {

final StackTraceElement[] stackTrace = entry.getValue();

//region changed code
threads.add(threadConverter.apply(thread, stackTrace));
//endregion
}

//region changed code
return threads;
}
//endregion

}
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,18 @@ public void reportExternalAdRevenue(@NonNull Object... values) {
});
}

public void reportAnr(@NonNull Map<Thread, StackTraceElement[]> allThreads) {
barrier.reportAnr(allThreads);
synchronousStageExecutor.reportAnr(allThreads);
List<Map.Entry<Thread, StackTraceElement[]>> entries = CollectionUtils.getListFromMap(allThreads);
getExecutor().execute(new Runnable() {
@Override
public void run() {
getMainReporter().reportAnr(CollectionUtils.getMapFromList(entries));
}
});
}

@VisibleForTesting
Barrier getMainFacadeBarrier() {
return barrier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ public void run() {
});
}

@Override
public void reportAnr(@NonNull Map<Thread, StackTraceElement[]> allThreads) {
barrier.reportAnr(allThreads);
synchronousStageExecutor.reportAnr(allThreads);
List<Map.Entry<Thread, StackTraceElement[]>> entries = CollectionUtils.getListFromMap(allThreads);
mExecutor.execute(new Runnable() {
@Override
public void run() {
getReporter().reportAnr(CollectionUtils.getMapFromList(entries));
}
});
}

@Override
public void reportAnr(@NonNull final AllThreads allThreads) {
barrier.reportAnr(allThreads);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,7 @@ class ReporterSynchronousStageExecutor {

fun reportAnr(allThreads: AllThreads) {}

fun reportAnr(allThread: Map<Thread, Array<StackTraceElement>>) {}

fun setSessionExtra(key: String, value: ByteArray?) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,6 @@ class SynchronousStageExecutor @VisibleForTesting constructor(
fun reportExternalAttribution(value: ExternalAttribution) {}

fun reportExternalAdRevenue(vararg values: Any) {}

fun reportAnr(allThreads: Map<Thread, Array<StackTraceElement>>) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ class Barrier(
NonNullValidator<ECommerceEvent>("ECommerceEvent")
)

private val anrAllThreadValidator = ThrowIfFailedValidator(
NonNullValidator<Map<Thread, Array<StackTraceElement>>>("Anr all threads")
)

fun enableActivityAutoTracking(application: Application?) {
applicationValidator.validate(application)
}
Expand Down Expand Up @@ -300,4 +304,8 @@ class Barrier(
fun reportExternalAdRevenue(vararg values: Any) {
activationValidator.validate()
}

fun reportAnr(allThread: Map<Thread, Array<StackTraceElement>>?) {
anrAllThreadValidator.validate(allThread)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class ReporterBarrier {
NonNullValidator<ECommerceEvent>("ECommerceEvent")
)

private val anrAllThreadValidator = ThrowIfFailedValidator(
NonNullValidator<Map<Thread, Array<StackTraceElement>>>("ANR all threads")
)

fun reportEvent(eventName: String?) {
eventNameValidator.validate(eventName)
}
Expand Down Expand Up @@ -120,6 +124,10 @@ class ReporterBarrier {

fun sendEventsBuffer() {}

fun reportAnr(allThreads: Map<Thread, Array<StackTraceElement>>?) {
anrAllThreadValidator.validate(allThreads)
}

fun reportAnr(allThreads: AllThreads?) {}

fun activate(config: ReporterConfig?) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ public void perform(@NonNull IReporterExtended reporter) {
});
}

@Override
public void reportAnr(@NonNull Map<Thread, StackTraceElement[]> allThreads) {
processCommand(new IReporterCommandPerformer() {
@Override
public void perform(@NonNull IReporterExtended reporter) {
reporter.reportAnr(allThreads);
}
});
}

@Override
public void reportAnr(@NonNull final AllThreads allThreads) {
processCommand(new IReporterCommandPerformer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ public void reportUnhandledException(@NonNull UnhandledException unhandledExcept
//Do nothing
}

@Override
public void reportAnr(@NonNull Map<Thread, StackTraceElement[]> allThreads) {
// Do nothing
}

@Override
public void reportAnr(@NonNull AllThreads allThreads) {
//Do nothing
Expand Down
Loading

0 comments on commit ec5e1f0

Please sign in to comment.