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 #1372

Merged
merged 2 commits into from
Sep 27, 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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## TBD

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

## 5.13.0 (2021-09-22)

* Capture breadcrumbs for OkHttp network requests
Expand Down
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,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.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.byDescriptor(MapUtils.<String>getOrThrow(map, "state")),
new Stacktrace(frames),
logger
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal class ThreadSerializer : MapSerializer<Thread> {
map["name"] = thread.name
map["type"] = thread.type.toString().toLowerCase(Locale.US)
map["errorReportingThread"] = thread.errorReportingThread
map["state"] = thread.state.descriptor

map["stacktrace"] = thread.stacktrace.map {
val frame = mutableMapOf<String, Any?>()
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