Skip to content

Commit 7b41e02

Browse files
committed
Basic watchdog that triggers thread dumps on puck jank
1 parent f30a797 commit 7b41e02

File tree

2 files changed

+131
-1
lines changed

2 files changed

+131
-1
lines changed

app/src/main/java/com/mapbox/maps/testapp/examples/LocationComponentAnimationActivity.kt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.mapbox.maps.testapp.examples
22

3+
import android.animation.Animator
4+
import android.animation.ValueAnimator
35
import android.os.Bundle
46
import android.os.Handler
57
import android.os.Looper
8+
import android.util.Log
69
import androidx.appcompat.app.AppCompatActivity
710
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
811
import com.mapbox.geojson.Point
@@ -33,6 +36,28 @@ class LocationComponentAnimationActivity : AppCompatActivity() {
3336
private inner class FakeLocationProvider : LocationProvider {
3437

3538
private var locationConsumer: LocationConsumer? = null
39+
private val listeners =
40+
object : ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
41+
override fun onAnimationUpdate(animation: ValueAnimator) {
42+
Watchdog.reschedule()
43+
}
44+
45+
override fun onAnimationStart(animation: Animator) {
46+
Watchdog.reschedule()
47+
}
48+
49+
override fun onAnimationEnd(animation: Animator) {
50+
Watchdog.stop()
51+
animation.removeListener(this)
52+
(animation as ValueAnimator).removeUpdateListener(this)
53+
}
54+
55+
override fun onAnimationCancel(animation: Animator) {
56+
}
57+
58+
override fun onAnimationRepeat(animation: Animator) {
59+
}
60+
}
3661

3762
private fun emitFakeLocations() {
3863
// after several first emits we update puck animator options
@@ -73,7 +98,10 @@ class LocationComponentAnimationActivity : AppCompatActivity() {
7398
POINT_LNG + delta,
7499
POINT_LAT + delta
75100
)
76-
)
101+
) {
102+
addUpdateListener(this@FakeLocationProvider.listeners)
103+
addListener(this@FakeLocationProvider.listeners)
104+
}
77105
}
78106
}
79107
locationConsumer?.onBearingUpdated(BEARING + delta * 10000.0 * 5)
@@ -88,11 +116,20 @@ class LocationComponentAnimationActivity : AppCompatActivity() {
88116
override fun registerLocationConsumer(locationConsumer: LocationConsumer) {
89117
this.locationConsumer = locationConsumer
90118
emitFakeLocations()
119+
Watchdog.enabled = true
120+
// Fake a busy main thread after 15s
121+
handler.postDelayed({
122+
Log.d("TAG", "emitFakeLocations: Blocking main thread")
123+
// Simulate main thread busy for few milliseconds
124+
Thread.sleep(150)
125+
Log.d("TAG", "emitFakeLocations: Finished blocking main thread")
126+
}, 15_000L)
91127
}
92128

93129
override fun unRegisterLocationConsumer(locationConsumer: LocationConsumer) {
94130
this.locationConsumer = null
95131
handler.removeCallbacksAndMessages(null)
132+
Watchdog.enabled = false
96133
}
97134
}
98135

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.mapbox.maps.testapp.examples
2+
3+
import android.os.Handler
4+
import android.os.HandlerThread
5+
import android.os.Message
6+
import android.os.Process
7+
import android.util.Log
8+
import com.mapbox.maps.testapp.examples.Watchdog.TIME_TO_FIRST_TRIGGER
9+
import com.mapbox.maps.testapp.examples.Watchdog.TIME_TO_SUBSEQUENT_TRIGGER
10+
import com.mapbox.maps.testapp.examples.Watchdog.reschedule
11+
import com.mapbox.maps.testapp.examples.Watchdog.stop
12+
13+
/**
14+
* A simple watchdog that will trigger a [Process.SIGNAL_QUIT] signal if [reschedule] is not called
15+
* within [TIME_TO_FIRST_TRIGGER] milliseconds and continues to do so every
16+
* [TIME_TO_SUBSEQUENT_TRIGGER] until [reschedule] or [stop] is called.
17+
*/
18+
object Watchdog {
19+
var enabled = false
20+
set(value) {
21+
if (field == value) return
22+
field = value
23+
if (value) {
24+
// Start the watchdog thread when enabled to avoid unnecessary overhead
25+
watchdogHandlerThread = HandlerThread(TAG).apply { start() }
26+
watchdogHandler = Handler(watchdogHandlerThread!!.looper)
27+
} else {
28+
// Stop the watchdog thread and free properties when disabled to avoid unnecessary overhead
29+
stop()
30+
watchdogHandlerThread?.quit()
31+
watchdogHandler = null
32+
watchdogHandlerThread = null
33+
}
34+
}
35+
36+
private var watchdogHandlerThread: HandlerThread? = null
37+
private var watchdogHandler: Handler? = null
38+
private var currentCounter = 0
39+
40+
private val quitSignalTask: () -> Unit = {
41+
Log.w(TAG, "(${currentCounter++}) Task not rescheduled on time. Triggering SIGNAL_QUIT.")
42+
// Send a quit signal to the current process to write a thread dump to `/data/anr/`.
43+
Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT)
44+
scheduleQuitSignalTask()
45+
}
46+
47+
private fun scheduleQuitSignalTask() {
48+
if (currentCounter >= MAX_CONSECUTIVE_TRIGGERS) {
49+
Log.w(
50+
TAG,
51+
"Max consecutive triggers ($currentCounter) reached. Not scheduling another trigger."
52+
)
53+
return
54+
}
55+
if (enabled) {
56+
watchdogHandler?.let {
57+
it.sendMessageDelayed(Message.obtain(it, quitSignalTask), TIME_TO_SUBSEQUENT_TRIGGER)
58+
}
59+
}
60+
}
61+
62+
fun reschedule() {
63+
if (enabled) {
64+
stop()
65+
watchdogHandler?.let {
66+
it.sendMessageDelayed(Message.obtain(it, quitSignalTask), TIME_TO_FIRST_TRIGGER)
67+
}
68+
}
69+
}
70+
71+
fun stop() {
72+
// Cancel all pending tasks
73+
watchdogHandler?.removeCallbacksAndMessages(null)
74+
currentCounter = 0
75+
}
76+
77+
private const val TAG = "Watchdog"
78+
79+
/**
80+
* The amount of time that need to pass before the watchdog triggers the first time.
81+
* That is, if [reschedule] is not called within this time, the watchdog task will trigger.
82+
* Unit is milliseconds.
83+
*/
84+
private const val TIME_TO_FIRST_TRIGGER: Long = 50L
85+
86+
/**
87+
* The amount of time that the task will wait before running again.
88+
* Unit is milliseconds.
89+
*/
90+
private const val TIME_TO_SUBSEQUENT_TRIGGER: Long = 100L
91+
92+
private const val MAX_CONSECUTIVE_TRIGGERS = 5
93+
}

0 commit comments

Comments
 (0)