-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'anr-detection' into anr-detection-ndk
- Loading branch information
Showing
19 changed files
with
417 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
Feature: Detects ANR | ||
|
||
Scenario: Test ANR detected with default timing | ||
When I run "AppNotRespondingScenario" | ||
Then I should receive a request | ||
And the request is a valid for the error reporting API | ||
And the exception "errorClass" equals "ANR" | ||
And the exception "message" equals "Application did not respond for at least 5000 ms" | ||
|
||
Scenario: Test ANR not detected when disabled | ||
When I run "AppNotRespondingDisabledScenario" | ||
Then I should receive 0 requests | ||
|
||
Scenario: Test ANR not detected under response time | ||
When I run "AppNotRespondingShortScenario" | ||
Then I should receive 0 requests | ||
|
||
Scenario: Test ANR wait time can be set to under default time | ||
When I run "AppNotRespondingShorterThresholdScenario" | ||
Then I should receive a request | ||
And the request is a valid for the error reporting API | ||
And the exception "errorClass" equals "ANR" | ||
And the exception "message" equals "Application did not respond for at least 2000 ms" | ||
|
||
Scenario: Test ANR wait time can be set to over default time | ||
When I run "AppNotRespondingLongerThresholdScenario" | ||
Then I should receive 0 requests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
...rc/main/java/com/bugsnag/android/mazerunner/scenarios/AppNotRespondingDisabledScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
|
||
/** | ||
* Stops the app from responding for a time period with ANR detection disabled | ||
*/ | ||
internal class AppNotRespondingDisabledScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
init { | ||
config.setAutoCaptureSessions(false) | ||
config.detectAnrs = false | ||
} | ||
|
||
override fun run() { | ||
super.run() | ||
Thread.sleep(6000) | ||
} | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
.../java/com/bugsnag/android/mazerunner/scenarios/AppNotRespondingLongerThresholdScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
|
||
/** | ||
* Stops the app from responding for a time period after changing the threshold to be higher | ||
*/ | ||
internal class AppNotRespondingLongerThresholdScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
init { | ||
config.setAutoCaptureSessions(false) | ||
config.detectAnrs = true | ||
config.anrThresholdMs = 8000L | ||
} | ||
|
||
override fun run() { | ||
super.run() | ||
Thread.sleep(6000) | ||
} | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
...runner/src/main/java/com/bugsnag/android/mazerunner/scenarios/AppNotRespondingScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
|
||
/** | ||
* Stops the app from responding for a time period | ||
*/ | ||
internal class AppNotRespondingScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
init { | ||
config.setAutoCaptureSessions(false) | ||
config.detectAnrs = true | ||
} | ||
|
||
override fun run() { | ||
super.run() | ||
Thread.sleep(6000) | ||
} | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
...r/src/main/java/com/bugsnag/android/mazerunner/scenarios/AppNotRespondingShortScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
|
||
/** | ||
* Stops the app from responding for a time period shorter than the default | ||
*/ | ||
internal class AppNotRespondingShortScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
init { | ||
config.setAutoCaptureSessions(false) | ||
config.detectAnrs = true | ||
} | ||
|
||
override fun run() { | ||
super.run() | ||
Thread.sleep(3000) | ||
} | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
...java/com/bugsnag/android/mazerunner/scenarios/AppNotRespondingShorterThresholdScenario.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.bugsnag.android.mazerunner.scenarios | ||
|
||
import android.content.Context | ||
import com.bugsnag.android.Bugsnag | ||
import com.bugsnag.android.Configuration | ||
|
||
/** | ||
* Stops the app from responding for a time period after changing the threshold to be lower | ||
*/ | ||
internal class AppNotRespondingShorterThresholdScenario(config: Configuration, | ||
context: Context) : Scenario(config, context) { | ||
init { | ||
config.setAutoCaptureSessions(false) | ||
config.detectAnrs = true | ||
config.anrThresholdMs = 2000L | ||
} | ||
|
||
override fun run() { | ||
super.run() | ||
Thread.sleep(3000) | ||
} | ||
|
||
} |
32 changes: 32 additions & 0 deletions
32
sdk/src/androidTest/java/com/bugsnag/android/AnrConfigTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.bugsnag.android | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertTrue | ||
import org.junit.Test | ||
|
||
class AnrConfigTest { | ||
|
||
private val config = Configuration("api-key") | ||
|
||
@Test | ||
fun testDetectAnrDefault() { | ||
assertTrue(config.detectAnrs) | ||
} | ||
|
||
/** | ||
* Verifies that attempts to set the ANR threshold below 1000ms set the value as 1000ms | ||
*/ | ||
@Test | ||
fun testAnrThresholdMs() { | ||
val config = config | ||
assertEquals(5000, config.anrThresholdMs) | ||
|
||
config.anrThresholdMs = 10000 | ||
assertEquals(10000, config.anrThresholdMs) | ||
|
||
arrayOf(1000, 999, 0, -5, Long.MIN_VALUE).forEach { | ||
config.anrThresholdMs = it | ||
assertEquals(1000, config.anrThresholdMs) | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
sdk/src/androidTest/java/com/bugsnag/android/BlockedThreadDetectorTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.bugsnag.android | ||
|
||
import android.os.Looper | ||
import org.junit.Test | ||
|
||
class BlockedThreadDetectorTest { | ||
|
||
private val looper = Looper.getMainLooper() | ||
|
||
@Test(expected = IllegalArgumentException::class) | ||
fun testInvalidBlockedThresholdMs() { | ||
BlockedThreadDetector(-1, 1, looper) {} | ||
} | ||
|
||
@Test(expected = IllegalArgumentException::class) | ||
fun testInvalidCheckIntervalMs() { | ||
BlockedThreadDetector(1, -1, looper) {} | ||
} | ||
|
||
@Test(expected = IllegalArgumentException::class) | ||
fun testInvalidThread() { | ||
BlockedThreadDetector(1, 1, null) {} | ||
} | ||
|
||
@Test(expected = IllegalArgumentException::class) | ||
fun testInvalidDelegate() { | ||
BlockedThreadDetector(1, 1, looper, null) | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
sdk/src/main/java/com/bugsnag/android/BlockedThreadDetector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package com.bugsnag.android; | ||
|
||
import android.os.Handler; | ||
import android.os.Looper; | ||
import android.os.SystemClock; | ||
|
||
/** | ||
* Detects whether a given thread is blocked by continuously posting a {@link Runnable} to it | ||
* from a watcher thread, invoking a delegate if the message is not processed within | ||
* a configured interval. | ||
*/ | ||
final class BlockedThreadDetector { | ||
|
||
static final int MIN_CHECK_INTERVAL_MS = 1000; | ||
|
||
interface Delegate { | ||
|
||
/** | ||
* Invoked when a given thread has been unable to execute a {@link Runnable} within | ||
* the {@link #blockedThresholdMs} | ||
* | ||
* @param thread the thread being monitored | ||
*/ | ||
void onThreadBlocked(Thread thread); | ||
} | ||
|
||
final Looper looper; | ||
final long checkIntervalMs; | ||
final long blockedThresholdMs; | ||
final Handler handler; | ||
final Delegate delegate; | ||
|
||
volatile long lastUpdateMs; | ||
volatile boolean isAlreadyBlocked = false; | ||
|
||
BlockedThreadDetector(long blockedThresholdMs, | ||
Looper looper, | ||
Delegate delegate) { | ||
this(blockedThresholdMs, MIN_CHECK_INTERVAL_MS, looper, delegate); | ||
} | ||
|
||
BlockedThreadDetector(long blockedThresholdMs, | ||
long checkIntervalMs, | ||
Looper looper, | ||
Delegate delegate) { | ||
if ((blockedThresholdMs <= 0 || checkIntervalMs <= 0 | ||
|| looper == null || delegate == null)) { | ||
throw new IllegalArgumentException(); | ||
} | ||
this.blockedThresholdMs = blockedThresholdMs; | ||
this.checkIntervalMs = checkIntervalMs; | ||
this.looper = looper; | ||
this.delegate = delegate; | ||
this.handler = new Handler(looper); | ||
} | ||
|
||
void updateLivenessTimestamp() { | ||
lastUpdateMs = SystemClock.elapsedRealtime(); | ||
} | ||
|
||
void start() { | ||
updateLivenessTimestamp(); | ||
handler.post(livenessCheck); | ||
watcherThread.start(); | ||
} | ||
|
||
final Runnable livenessCheck = new Runnable() { | ||
@Override | ||
public void run() { | ||
updateLivenessTimestamp(); | ||
handler.postDelayed(this, checkIntervalMs); | ||
} | ||
}; | ||
|
||
final Thread watcherThread = new Thread() { | ||
@Override | ||
public void run() { | ||
while (!isInterrupted()) { | ||
// when we would next consider the app blocked if no timestamp updates take place | ||
long now = SystemClock.elapsedRealtime(); | ||
long nextCheckIn = Math.max(lastUpdateMs + blockedThresholdMs - now, 0); | ||
|
||
try { | ||
Thread.sleep(nextCheckIn); // throttle checks to the configured threshold | ||
} catch (InterruptedException exc) { | ||
interrupt(); | ||
} | ||
checkIfThreadBlocked(); | ||
} | ||
} | ||
|
||
private void checkIfThreadBlocked() { | ||
long delta = SystemClock.elapsedRealtime() - lastUpdateMs; | ||
|
||
if (delta > blockedThresholdMs) { | ||
if (!isAlreadyBlocked) { | ||
delegate.onThreadBlocked(looper.getThread()); | ||
} | ||
isAlreadyBlocked = true; // prevents duplicate reports for the same ANR | ||
} else { | ||
isAlreadyBlocked = false; | ||
} | ||
} | ||
}; | ||
} |
Oops, something went wrong.