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

Capture and report Thread.state for Android Runtime threads #1367

Merged
merged 3 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* Update project to build using Gradle/AGP 7
[#1354](https://github.com/bugsnag/bugsnag-android/pull/1354)

* Capture and report Thread.state for Android Runtime threads
[#1367](https://github.com/bugsnag/bugsnag-android/pull/1367)

## 5.12.0 (2021-08-26)

* The `app.lowMemory` value always report the most recent `onTrimMemory`/`onLowMemory` status
Expand Down
18 changes: 10 additions & 8 deletions bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
<?xml version="1.0" ?>
<?xml version='1.0' encoding='UTF-8'?>
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ImplicitDefaultLocale:DeliveryHeaders.kt$String.format("%02x", byte)</ID>
<ID>LongParameterList:App.kt$App$( /** * The architecture of the running application binary */ var binaryArch: String?, /** * The package name of the application */ var id: String?, /** * The release stage set in [Configuration.releaseStage] */ var releaseStage: String?, /** * The version of the application set in [Configuration.version] */ var version: String?, /** The revision ID from the manifest (React Native apps only) */ var codeBundleId: String?, /** * The unique identifier for the build of the application set in [Configuration.buildUuid] */ var buildUuid: String?, /** * The application type set in [Configuration#version] */ var type: String?, /** * The version code of the application set in [Configuration.versionCode] */ var versionCode: Number? )</ID>
<ID>LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: SessionTracker, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val memoryTrimState: MemoryTrimState )</ID>
<ID>LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: String?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ var duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ var durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ var inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ var isLaunching: Boolean? )</ID>
<ID>LongParameterList:AppWithState.kt$AppWithState$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? )</ID>
<ID>LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceId: String?, memoryTrimState: MemoryTrimState )</ID>
<ID>LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array&lt;String&gt;?, /** * Whether the device has been jailbroken */ var jailbroken: Boolean?, /** * A UUID generated by Bugsnag and used for the individual application on a device */ var id: String?, /** * The IETF language tag of the locale used */ var locale: String?, /** * The total number of bytes of memory on the device */ var totalMemory: Long?, /** * A collection of names and their versions of the primary languages, frameworks or * runtimes that the application is running on */ var runtimeVersions: MutableMap&lt;String, Any&gt;? )</ID>
<ID>LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array&lt;String&gt;? )</ID>
<ID>LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array&lt;String>?, /** * Whether the device has been jailbroken */ var jailbroken: Boolean?, /** * A UUID generated by Bugsnag and used for the individual application on a device */ var id: String?, /** * The IETF language tag of the locale used */ var locale: String?, /** * The total number of bytes of memory on the device */ var totalMemory: Long?, /** * A collection of names and their versions of the primary languages, frameworks or * runtimes that the application is running on */ var runtimeVersions: MutableMap&lt;String, Any>? )</ID>
<ID>LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array&lt;String>? )</ID>
<ID>LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceId: String?, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, rootDetector: RootDetector, private val bgTaskService: BackgroundTaskService, private val logger: Logger )</ID>
<ID>LongParameterList:DeviceWithState.kt$DeviceWithState$( buildInfo: DeviceBuildInfo, jailbroken: Boolean?, id: String?, locale: String?, totalMemory: Long?, runtimeVersions: MutableMap&lt;String, Any&gt;, /** * The number of free bytes of storage available on the device */ var freeDisk: Long?, /** * The number of free bytes of memory available on the device */ var freeMemory: Long?, /** * The orientation of the device when the event occurred: either portrait or landscape */ var orientation: String?, /** * The timestamp on the device when the event occurred */ var time: Date? )</ID>
<ID>LongParameterList:DeviceWithState.kt$DeviceWithState$( buildInfo: DeviceBuildInfo, jailbroken: Boolean?, id: String?, locale: String?, totalMemory: Long?, runtimeVersions: MutableMap&lt;String, Any>, /** * The number of free bytes of storage available on the device */ var freeDisk: Long?, /** * The number of free bytes of memory available on the device */ var freeMemory: Long?, /** * The orientation of the device when the event occurred: either portrait or landscape */ var orientation: String?, /** * The timestamp on the device when the event occurred */ var time: Date? )</ID>
<ID>LongParameterList:EventFilenameInfo.kt$EventFilenameInfo.Companion$( obj: Any, uuid: String = UUID.randomUUID().toString(), apiKey: String?, timestamp: Long = System.currentTimeMillis(), config: ImmutableConfig, isLaunching: Boolean? = null )</ID>
<ID>LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier )</ID>
<ID>LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int )</ID>
<ID>LongParameterList:ThreadState.kt$ThreadState$( stackTraces: MutableMap&lt;java.lang.Thread, Array&lt;StackTraceElement&gt;&gt;, currentThread: java.lang.Thread, exc: Throwable?, isUnhandled: Boolean, projectPackages: Collection&lt;String&gt;, logger: Logger )</ID>
<ID>LongParameterList:ThreadState.kt$ThreadState$( stackTraces: MutableMap&lt;java.lang.Thread, Array&lt;StackTraceElement>>, currentThread: java.lang.Thread, exc: Throwable?, isUnhandled: Boolean, projectPackages: Collection&lt;String>, logger: Logger )</ID>
<ID>MagicNumber:DefaultDelivery.kt$DefaultDelivery$299</ID>
<ID>MagicNumber:DefaultDelivery.kt$DefaultDelivery$429</ID>
<ID>MagicNumber:DefaultDelivery.kt$DefaultDelivery$499</ID>
<ID>MagicNumber:LastRunInfoStore.kt$LastRunInfoStore$3</ID>
<ID>MaxLineLength:EventSerializationTest.kt$EventSerializationTest.Companion$it.threads.add(Thread(5, "main", ThreadType.ANDROID, true, Thread.State.RUNNABLE, stacktrace, NoopLogger))</ID>
<ID>MaxLineLength:LastRunInfo.kt$LastRunInfo$return "LastRunInfo(consecutiveLaunchCrashes=$consecutiveLaunchCrashes, crashed=$crashed, crashedDuringLaunch=$crashedDuringLaunch)"</ID>
<ID>ProtectedMemberInFinalClass:ConfigInternal.kt$ConfigInternal$protected val plugins = HashSet&lt;Plugin&gt;()</ID>
<ID>MaxLineLength:ThreadState.kt$ThreadState$Thread(thread.id, thread.name, ThreadType.ANDROID, errorThread, Thread.State.forThread(thread), stacktrace, logger)</ID>
<ID>ProtectedMemberInFinalClass:ConfigInternal.kt$ConfigInternal$protected val plugins = HashSet&lt;Plugin>()</ID>
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun isAnr(event: Event): Boolean</ID>
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun shouldDiscardClass(): Boolean</ID>
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityInternal(severity: Severity)</ID>
<ID>ProtectedMemberInFinalClass:EventInternal.kt$EventInternal$protected fun updateSeverityReason(@SeverityReason.SeverityReasonType reason: String)</ID>
<ID>ReturnCount:DefaultDelivery.kt$DefaultDelivery$fun deliver( urlString: String, streamable: JsonStream.Streamable, headers: Map&lt;String, String?&gt; ): DeliveryStatus</ID>
<ID>ReturnCount:DefaultDelivery.kt$DefaultDelivery$fun deliver( urlString: String, streamable: JsonStream.Streamable, headers: Map&lt;String, String?> ): DeliveryStatus</ID>
<ID>SwallowedException:ConnectivityCompat.kt$ConnectivityLegacy$catch (e: NullPointerException) { // in some rare cases we get a remote NullPointerException via Parcel.readException null }</ID>
<ID>SwallowedException:ContextExtensions.kt$catch (exc: RuntimeException) { null }</ID>
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: Exception) { false }</ID>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bugsnag.android;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.IOException;
import java.util.List;
Expand All @@ -19,9 +20,10 @@ public class Thread implements JsonStream.Streamable {
@NonNull String name,
@NonNull ThreadType type,
boolean errorReportingThread,
@NonNull Thread.State state,
@NonNull Stacktrace stacktrace,
@NonNull Logger logger) {
this.impl = new ThreadInternal(id, name, type, errorReportingThread, stacktrace);
this.impl = new ThreadInternal(id, name, type, errorReportingThread, state, stacktrace);
this.logger = logger;
}

Expand Down Expand Up @@ -81,6 +83,25 @@ public ThreadType getType() {
return impl.getType();
}

/**
* Sets the state of thread (from {@link java.lang.Thread})
*/
public void setState(@NonNull Thread.State threadState) {
if (threadState != null) {
impl.setState(threadState);
} else {
logNull("state");
}
}

/**
* Gets the state of the thread (from {@link java.lang.Thread})
*/
@NonNull
public Thread.State getState() {
return impl.getState();
}

/**
* Gets whether the thread was the thread that caused the event
*/
Expand Down Expand Up @@ -111,4 +132,79 @@ public List<Stackframe> getStacktrace() {
public void toStream(@NonNull JsonStream stream) throws IOException {
impl.toStream(stream);
}

/**
* The state of a reported {@link Thread}. These states correspond directly to
* {@link java.lang.Thread.State}, except for {@code UNKNOWN} which indicates that
* a state could not be captured or mapped.
*/
public enum State {
NEW("new"),
BLOCKED("blocked"),
RUNNABLE("runnable"),
TERMINATED("terminated"),
TIMED_WAITING("timed waiting"),
WAITING("waiting"),
UNKNOWN("unknown");

private final String descriptor;

State(String descriptor) {
this.descriptor = descriptor;
}

@NonNull
public String getDescriptor() {
return descriptor;
}

@NonNull
public static State forThread(@NonNull java.lang.Thread thread) {
java.lang.Thread.State state = thread.getState();
return getState(state);
}

/**
* Lookup the {@code State} for a given {@link #getDescriptor() descriptor} code. Unlike
* {@link #valueOf(String) valueOf}, this method will return {@link #UNKNOWN} is no
* matching {@code State} constant can be found.
*
* @param descriptor a consistent descriptor of the state constant to lookup
* @return the requested {@link State} or {@link #UNKNOWN}
*/
@NonNull
public static State byDescriptor(@Nullable String descriptor) {
if (descriptor == null) {
return UNKNOWN;
}

for (State state : values()) {
if (state.getDescriptor().equals(descriptor)) {
return state;
}
}

return UNKNOWN;
}

@NonNull
private static State getState(java.lang.Thread.State state) {
switch (state) {
case NEW:
return NEW;
case BLOCKED:
return BLOCKED;
case RUNNABLE:
return RUNNABLE;
case TERMINATED:
return TERMINATED;
case TIMED_WAITING:
return TIMED_WAITING;
case WAITING:
return WAITING;
default:
return UNKNOWN;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ThreadInternal internal constructor(
var name: String,
var type: ThreadType,
val isErrorReportingThread: Boolean,
var state: Thread.State,
stacktrace: Stacktrace
) : JsonStream.Streamable {

Expand All @@ -18,6 +19,7 @@ class ThreadInternal internal constructor(
writer.name("id").value(id)
writer.name("name").value(name)
writer.name("type").value(type.desc)
writer.name("state").value(state.descriptor)

writer.name("stacktrace")
writer.beginArray()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ internal class ThreadState @Suppress("LongParameterList") @JvmOverloads construc
if (trace != null) {
val stacktrace = Stacktrace(trace, projectPackages, logger)
val errorThread = thread.id == currentThreadId
Thread(thread.id, thread.name, ThreadType.ANDROID, errorThread, stacktrace, logger)
Thread(thread.id, thread.name, ThreadType.ANDROID, errorThread, Thread.State.forThread(thread), stacktrace, logger)
} else {
null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class EventSerializationTest {
createEvent {
val stacktrace = Stacktrace(arrayOf(), emptySet(), NoopLogger)
it.threads.clear()
it.threads.add(Thread(5, "main", ThreadType.ANDROID, true, stacktrace, NoopLogger))
it.threads.add(Thread(5, "main", ThreadType.ANDROID, true, Thread.State.RUNNABLE, stacktrace, NoopLogger))
},

// threads included
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ public void setUp() {
logger = new InterceptingLogger();
List<Stackframe> frames = Collections.emptyList();
stacktrace = new Stacktrace(frames);
thread = new Thread(1, "thread-2", ThreadType.ANDROID, false, stacktrace, logger);
thread = new Thread(
1,
"thread-2",
ThreadType.ANDROID,
false,
Thread.State.RUNNABLE,
stacktrace,
logger
);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal class ThreadSerializationTest {
"main-one",
ThreadType.ANDROID,
true,
Thread.State.RUNNABLE,
Stacktrace(
stacktrace,
emptySet(),
Expand All @@ -43,6 +44,7 @@ internal class ThreadSerializationTest {
"main-one",
ThreadType.ANDROID,
false,
Thread.State.RUNNABLE,
Stacktrace(
stacktrace1,
emptySet(),
Expand Down Expand Up @@ -76,6 +78,7 @@ internal class ThreadSerializationTest {
"main-one",
ThreadType.ANDROID,
true,
Thread.State.BLOCKED,
trace,
NoopLogger
)
Expand All @@ -98,6 +101,7 @@ internal class ThreadSerializationTest {
"main-one",
ThreadType.ANDROID,
false,
Thread.State.WAITING,
trace,
NoopLogger
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"id": 5,
"name": "main",
"type": "android",
"state": "runnable",
"stacktrace": [],
"errorReportingThread": true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": 24,
"name": "main-one",
"type": "android",
"state": "runnable",
"stacktrace": [
{
"method": "run_func",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": 24,
"name": "main-one",
"type": "android",
"state": "runnable",
"stacktrace": [
{
"method": "run_func",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": 24,
"name": "main-one",
"type": "android",
"state": "blocked",
"stacktrace": [
{
"method": "run_func",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": 24,
"name": "main-one",
"type": "android",
"state": "waiting",
"stacktrace": [
{
"method": "run_func",
Expand Down
6 changes: 3 additions & 3 deletions bugsnag-plugin-android-anr/detekt-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?xml version="1.0" ?>
<?xml version='1.0' encoding='UTF-8'?>
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>SwallowedException:AnrDetailsCollector.kt$AnrDetailsCollector$catch (exc: RuntimeException) { null }</ID>
<ID>SwallowedException:AnrPlugin.kt$AnrPlugin$catch (exc: Throwable) { null }</ID>
<ID>ThrowingExceptionsWithoutMessageOrCause:AnrPlugin.kt$AnrPlugin$RuntimeException()</ID>
<ID>UnusedPrivateMember:AnrPlugin.kt$AnrPlugin$ private fun notifyAnrDetected(nativeTrace: List&lt;NativeStackframe&gt;)</ID>
<ID>UnusedPrivateMember:AnrPlugin.kt$AnrPlugin$ private fun notifyAnrDetected(nativeTrace: List&lt;NativeStackframe>)</ID>
</CurrentIssues>
</SmellBaseline>
4 changes: 2 additions & 2 deletions bugsnag-plugin-android-ndk/detekt-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" ?>
<?xml version='1.0' encoding='UTF-8'?>
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComplexMethod:NativeBridge.kt$NativeBridge$override fun onStateChange(event: StateEvent)</ID>
<ID>LongParameterList:NativeBridge.kt$NativeBridge$( apiKey: String, reportingDirectory: String, lastRunInfoPath: String, consecutiveLaunchCrashes: Int, autoDetectNdkCrashes: Boolean, apiLevel: Int, is32bit: Boolean, appVersion: String, buildUuid: String, releaseStage: String )</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public Thread deserialize(Map<String, Object> map) {
MapUtils.<String>getOrThrow(map, "name"),
ThreadType.valueOf(type.toUpperCase(Locale.US)),
errorReportingThread,
Thread.State.byDescriptor(MapUtils.<String>getOrThrow(map, "state")),
new Stacktrace(frames),
logger
);
Expand Down
Loading