Skip to content

Commit

Permalink
Add the pretruncation framecount to ThreadInfo (#1359)
Browse files Browse the repository at this point in the history
## Goal

Send the pre-truncation frame count via the ThreadInfo. This affects ANRs but all the other stacktraces as well that use that POJO.
  • Loading branch information
bidetofevil authored Sep 12, 2024
1 parent b000978 commit 624e6dc
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class AnrBehaviorImpl(
private const val DEFAULT_ANR_PROCESS_ERRORS_SCHEDULER_EXTRA_TIME_ALLOWANCE: Long =
30 * 1000
private const val DEFAULT_ANR_MAX_PER_INTERVAL = 80
private const val DEFAULT_STACKTRACE_FRAME_LIMIT = 100
private const val DEFAULT_STACKTRACE_FRAME_LIMIT = 200
private const val DEFAULT_ANR_MIN_THREAD_PRIORITY_TO_CAPTURE = 0
private const val DEFAULT_ANR_MAX_ANR_INTERVALS_PER_SESSION = 5
private const val DEFAULT_ANR_MIN_CAPTURE_DURATION = 1000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ internal class AnrBehaviorImplTest {
assertEquals(5000, getAnrProcessErrorsDelayMs())
assertEquals(30000, getAnrProcessErrorsSchedulerExtraTimeAllowanceMs())
assertEquals(80, getMaxStacktracesPerInterval())
assertEquals(100, getStacktraceFrameLimit())
assertEquals(200, getStacktraceFrameLimit())
assertEquals(5, getMaxAnrIntervalsPerSession())
assertEquals(0, getMinThreadPriority())
assertEquals(1000, getMinDuration())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ class AnrOtelMapper(
sample.threads?.singleOrNull()?.let { thread ->
attrs.add(Attribute(JvmAttributes.JVM_THREAD_STATE.key, thread.state.toString()))
attrs.add(Attribute("thread_priority", thread.priority.toString()))
attrs.add(Attribute("frame_count", thread.frameCount.toString()))

thread.lines?.let { lines ->
attrs.add(Attribute("frame_count", lines.size.toString()))
attrs.add(Attribute(ExceptionAttributes.EXCEPTION_STACKTRACE.key, lines.joinToString("\n")))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ internal class AnrIntervalTest {
listOf(
"java.base/java.lang.Thread.getStackTrace(Thread.java:1602)",
"io.embrace.android.embracesdk.ThreadInfoTest.testThreadInfoSerialization(ThreadInfoTest.kt:18)"
)
),
2
)

private val anrSample = AnrSample(150980980980, listOf(threadInfo), 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,13 @@ internal class AnrOtelMapperTest {
lines = listOf(
"com.example.app.MainActivity.onCreate(MainActivity.kt:10)",
"com.example.app.MainActivity.onCreate(MainActivity.kt:20)"
)
),
frameCount = 2
)

private val threads = listOf(stacktrace)
private val truncatedStack = stacktrace.copy(frameCount = 10000)
private val truncatedThreads = listOf(truncatedStack)

private val firstSample = AnrSample(
timestamp = FIRST_SAMPLE_MS,
Expand All @@ -60,13 +63,27 @@ internal class AnrOtelMapperTest {
threads = threads
)

private val truncatedSecondSample = AnrSample(
timestamp = SECOND_SAMPLE_MS,
sampleOverheadMs = SECOND_SAMPLE_OVERHEAD_MS,
code = AnrSample.CODE_DEFAULT,
threads = truncatedThreads
)

private val completedInterval = AnrInterval(
startTime = START_TIME_MS,
endTime = END_TIME_MS,
code = AnrInterval.CODE_DEFAULT,
anrSampleList = AnrSampleList(listOf(firstSample, secondSample))
)

private val completedIntervalWithTruncatedSample = AnrInterval(
startTime = START_TIME_MS,
endTime = END_TIME_MS,
code = AnrInterval.CODE_DEFAULT,
anrSampleList = AnrSampleList(listOf(firstSample, truncatedSecondSample))
)

private val inProgressInterval =
completedInterval.copy(endTime = null, lastKnownTime = LAST_KNOWN_TIME)

Expand Down Expand Up @@ -176,6 +193,15 @@ internal class AnrOtelMapperTest {
}
}

@Test
fun `truncated stack shows the pre-truncated frame count`() {
anrService.data = listOf(completedIntervalWithTruncatedSample)
val spans = mapper.snapshot(false)
val events = checkNotNull(spans.single().events)
assertEquals(2, events.size)
assertSampleMapped(events[1], truncatedSecondSample)
}

private fun Span.assertCommonOtelCharacteristics() {
assertNotNull(traceId)
assertNotNull(spanId)
Expand All @@ -201,7 +227,7 @@ internal class AnrOtelMapperTest {
val thread = checkNotNull(sample.threads?.single())
assertEquals(thread.state.toString(), attrs.findAttribute(JvmAttributes.JVM_THREAD_STATE.key).data)
assertEquals(thread.priority, attrs.findAttribute("thread_priority").data?.toInt())
assertEquals(thread.lines?.size, attrs.findAttribute("frame_count").data?.toInt())
assertEquals(thread.frameCount, attrs.findAttribute("frame_count").data?.toInt())
assertEquals(thread.lines?.joinToString("\n"), attrs.findAttribute(ExceptionAttributes.EXCEPTION_STACKTRACE.key).data)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import java.util.regex.Pattern

internal class ThreadInfoCollectorTest {

private val highPrioThread = ThreadInfo(1, RUNNABLE, "thread-1", MAX_PRIORITY, emptyList())
private val medPrioThread = ThreadInfo(2, RUNNABLE, "thread-2", NORM_PRIORITY, emptyList())
private val lowPrioThread = ThreadInfo(3, RUNNABLE, "thread-3", MIN_PRIORITY, emptyList())
private val highPrioThread = ThreadInfo(1, RUNNABLE, "thread-1", MAX_PRIORITY, emptyList(), 0)
private val medPrioThread = ThreadInfo(2, RUNNABLE, "thread-2", NORM_PRIORITY, emptyList(), 0)
private val lowPrioThread = ThreadInfo(3, RUNNABLE, "thread-3", MIN_PRIORITY, emptyList(), 0)

private lateinit var configService: ConfigService
private lateinit var threadInfoCollector: ThreadInfoCollector
Expand All @@ -30,7 +30,8 @@ internal class ThreadInfoCollectorTest {
configService = FakeConfigService(
anrBehavior = FakeAnrBehavior(
allowPatternList = listOf(currentThread().name).map(Pattern::compile),
blockPatternList = listOf("Finalizer").map(Pattern::compile)
blockPatternList = listOf("Finalizer").map(Pattern::compile),
frameLimit = 5
)
)
threadInfoCollector = ThreadInfoCollector(currentThread())
Expand Down Expand Up @@ -126,4 +127,12 @@ internal class ThreadInfoCollectorTest {
)
)
}

@Test
fun `verify truncation of ANR stacktrace respects the config`() {
val thread = threadInfoCollector.getAllowedThreads(configService).single()
val frames = checkNotNull(thread.lines)
assertEquals(5, frames.size)
assertTrue(thread.frameCount > frames.size)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,16 @@ data class ThreadInfo(
* String representation of each line of the stack trace.
*/
@Json(name = "tt")
val lines: List<String>?
val lines: List<String>?,

/**
* The total number of frames in the stack before truncation
*/
@Json(name = "fc")
val frameCount: Int,
) {

companion object {

/**
* Creates a [ThreadInfo] from the [Thread], [StackTraceElement][] pair,
* using the thread name and priority, and each stacktrace element as each line with a limited length.
*
* @param thread the exception
* @param maxStacktraceSize the maximum lines of a stacktrace
* @return the stacktrace instance
*/
/**
* Creates a [ThreadInfo] from the [Thread], [StackTraceElement][] pair,
* using the thread name and priority, and each stacktrace element as each line.
Expand All @@ -64,8 +61,9 @@ data class ThreadInfo(
): ThreadInfo {
val name = thread.name
val priority = thread.priority
val frameCount = stackTraceElements.size
val lines = stackTraceElements.take(maxStacktraceSize).map(StackTraceElement::toString)
return ThreadInfo(thread.id, thread.state, name, priority, lines)
return ThreadInfo(thread.id, thread.state, name, priority, lines, frameCount)
}
}
}
Loading

0 comments on commit 624e6dc

Please sign in to comment.