Skip to content

Commit 907a1e9

Browse files
authored
Speed up AnrWatcherTest and migrate to kotlin (#877)
* Rename .java to .kt * migrate to kotlin * remove old java test * overload timeout to speed up test * fix test
1 parent 1c2cf6e commit 907a1e9

File tree

5 files changed

+170
-163
lines changed

5 files changed

+170
-163
lines changed

instrumentation/anr/src/main/java/io/opentelemetry/android/instrumentation/anr/AnrWatcher.java

Lines changed: 0 additions & 59 deletions
This file was deleted.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.android.instrumentation.anr
7+
8+
import android.os.Handler
9+
import io.opentelemetry.context.Context
10+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
11+
import java.util.concurrent.CountDownLatch
12+
import java.util.concurrent.TimeUnit
13+
import java.util.concurrent.TimeUnit.SECONDS
14+
import java.util.concurrent.atomic.AtomicInteger
15+
16+
internal val DEFAULT_POLL_DURATION_NS = SECONDS.toNanos(1)
17+
18+
/**
19+
* Class that watches the ui thread for ANRs by posting
20+
* Runnables to the main thread. If 5 consecutive responses
21+
* time out, then an ANR is detected.
22+
*
23+
* @param pollDurationNs - exists for testing
24+
*/
25+
internal class AnrWatcher(
26+
private val uiHandler: Handler,
27+
private val mainThread: Thread,
28+
private val instrumenter: Instrumenter<Array<StackTraceElement>, Void>,
29+
private val pollDurationNs: Long = DEFAULT_POLL_DURATION_NS,
30+
) : Runnable {
31+
private val anrCounter = AtomicInteger()
32+
33+
constructor(uiHandler: Handler, mainThread: Thread, instrumenter: Instrumenter<Array<StackTraceElement>, Void>) :
34+
this(uiHandler, mainThread, instrumenter, DEFAULT_POLL_DURATION_NS)
35+
36+
override fun run() {
37+
val response = CountDownLatch(1)
38+
if (!uiHandler.post { response.countDown() }) {
39+
// the main thread is probably shutting down. ignore and return.
40+
return
41+
}
42+
val success: Boolean
43+
try {
44+
success = response.await(pollDurationNs, TimeUnit.NANOSECONDS)
45+
} catch (e: InterruptedException) {
46+
return
47+
}
48+
if (success) {
49+
anrCounter.set(0)
50+
return
51+
}
52+
if (anrCounter.incrementAndGet() >= 5) {
53+
val stackTrace = mainThread.stackTrace
54+
recordAnr(stackTrace)
55+
// only report once per 5s.
56+
anrCounter.set(0)
57+
}
58+
}
59+
60+
private fun recordAnr(stackTrace: Array<StackTraceElement>) {
61+
val context = instrumenter.start(Context.current(), stackTrace)
62+
instrumenter.end(context, stackTrace, null, null)
63+
}
64+
}

instrumentation/anr/src/test/java/io/opentelemetry/android/instrumentation/anr/AnrDetectorTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import static org.mockito.ArgumentMatchers.eq;
1111
import static org.mockito.ArgumentMatchers.isA;
1212
import static org.mockito.Mockito.verify;
13+
import static org.mockito.Mockito.when;
1314

1415
import android.os.Looper;
1516
import io.opentelemetry.android.internal.services.applifecycle.AppLifecycle;
@@ -32,6 +33,7 @@ class AnrDetectorTest {
3233

3334
@Test
3435
void shouldInstallInstrumentation() {
36+
when(mainLooper.getThread()).thenReturn(new Thread());
3537
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder().build();
3638

3739
AnrDetector anrDetector =

instrumentation/anr/src/test/java/io/opentelemetry/android/instrumentation/anr/AnrWatcherTest.java

Lines changed: 0 additions & 104 deletions
This file was deleted.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.android.instrumentation.anr
7+
8+
import android.os.Handler
9+
import io.mockk.Called
10+
import io.mockk.every
11+
import io.mockk.mockk
12+
import io.mockk.verify
13+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter
14+
import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension
15+
import org.junit.Before
16+
import org.junit.Test
17+
import org.junit.jupiter.api.extension.RegisterExtension
18+
19+
class AnrWatcherTest {
20+
@JvmField
21+
@RegisterExtension
22+
val testing = OpenTelemetryExtension.create()
23+
24+
private lateinit var handler: Handler
25+
private lateinit var mainThread: Thread
26+
private lateinit var instrumenter: Instrumenter<Array<StackTraceElement>, Void>
27+
28+
@Before
29+
fun setup() {
30+
handler = mockk()
31+
mainThread = mockk()
32+
instrumenter = mockk()
33+
}
34+
35+
@Test
36+
fun mainThreadDisappearing() {
37+
val anrWatcher = AnrWatcher(handler, mainThread, instrumenter)
38+
for (i in 0..4) {
39+
every { handler.post(any()) } returns false
40+
anrWatcher.run()
41+
}
42+
verify { instrumenter wasNot Called }
43+
}
44+
45+
@Test
46+
fun noAnr() {
47+
val anrWatcher = AnrWatcher(handler, mainThread, instrumenter)
48+
for (i in 0..4) {
49+
every { handler.post(any()) } answers {
50+
val callback = it.invocation.args[0] as Runnable
51+
callback.run()
52+
true
53+
}
54+
55+
anrWatcher.run()
56+
}
57+
verify { instrumenter wasNot Called }
58+
}
59+
60+
@Test
61+
fun noAnr_temporaryPause() {
62+
val anrWatcher = AnrWatcher(handler, mainThread, instrumenter)
63+
for (i in 0..4) {
64+
val index = i
65+
every { handler.post(any()) } answers {
66+
val callback = it.invocation.args[0] as Runnable
67+
// have it fail once
68+
if (index != 3) {
69+
callback.run()
70+
}
71+
true
72+
}
73+
anrWatcher.run()
74+
}
75+
verify { instrumenter wasNot Called }
76+
}
77+
78+
@Test
79+
fun anr_detected() {
80+
val stackTrace: Array<StackTraceElement> = arrayOf()
81+
every { instrumenter.start(any(), any()) } returns mockk()
82+
every { instrumenter.end(any(), any(), any(), any()) } returns mockk()
83+
every { mainThread.stackTrace } returns stackTrace
84+
85+
val anrWatcher = AnrWatcher(handler, mainThread, instrumenter, 1)
86+
every { handler.post(any()) } returns true
87+
for (i in 0..4) {
88+
anrWatcher.run()
89+
}
90+
verify(exactly = 1) { instrumenter.start(any(), refEq(stackTrace)) }
91+
verify(exactly = 1) { instrumenter.end(any(), refEq(stackTrace), isNull(), isNull()) }
92+
for (i in 0..3) {
93+
anrWatcher.run()
94+
}
95+
// Still just the 1 time
96+
verify(exactly = 1) { instrumenter.start(any(), refEq(stackTrace)) }
97+
verify(exactly = 1) { instrumenter.end(any(), refEq(stackTrace), isNull(), isNull()) }
98+
99+
anrWatcher.run()
100+
101+
verify(exactly = 2) { instrumenter.start(any(), refEq(stackTrace)) }
102+
verify(exactly = 2) { instrumenter.end(any(), refEq(stackTrace), isNull(), isNull()) }
103+
}
104+
}

0 commit comments

Comments
 (0)