Skip to content

Commit

Permalink
feat(threads): capture and report Thread.state for Android Runtime …
Browse files Browse the repository at this point in the history
…threads
  • Loading branch information
lemnik committed Sep 24, 2021
1 parent ff8809d commit 0065ec3
Show file tree
Hide file tree
Showing 16 changed files with 112 additions and 5 deletions.
2 changes: 2 additions & 0 deletions bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
<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>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>
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,66 @@ 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,
BLOCKED,
RUNNABLE,
TERMINATED,
TIMED_WAITING,
WAITING,
UNKNOWN;

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

/**
* An exception-safe wrapper for {@link #valueOf(String)} which also handles {@code null}
* names. This method is used in-preference to the standard {@code valueOf} as it will
* return {@link #UNKNOWN} instead of throwing an exception.
*
* @param name the name of the state constant to lookup
* @return the named {@link State} or {@link #UNKNOWN}
*/
@NonNull
public static State byName(@Nullable String name) {
if (name == null) {
return UNKNOWN;
}

try {
return valueOf(name);
} catch (IllegalArgumentException iae) {
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.name)

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.RUNNABLE,
trace,
NoopLogger
)
Expand All @@ -98,6 +101,7 @@ internal class ThreadSerializationTest {
"main-one",
ThreadType.ANDROID,
false,
Thread.State.RUNNABLE,
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": "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 @@ -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.byName(MapUtils.<String>getOrThrow(map, "state")),
new Stacktrace(frames),
logger
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class EventDeserializerTest {
"id" to 52L,
"type" to "reactnativejs",
"name" to "thread-worker-02",
"state" to "RUNNABLE",
"errorReportingThread" to true
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ThreadDeserializerTest {
map["id"] = 52L
map["type"] = "reactnativejs"
map["name"] = "thread-worker-02"
map["state"] = "RUNNABLE"
map["errorReportingThread"] = true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void setup() {
List<Stackframe> frames = Collections.singletonList(stackframe);
Stacktrace stacktrace = new Stacktrace(frames);
thread = new Thread(1, "fake-thread", ThreadType.ANDROID,
true, stacktrace, NoopLogger.INSTANCE);
true, Thread.State.RUNNABLE, stacktrace, NoopLogger.INSTANCE);
}

@Test
Expand Down

0 comments on commit 0065ec3

Please sign in to comment.