Skip to content

Commit 9acf4ba

Browse files
authored
Merge pull request #2582 from DataDog/tvaleev/feature/RUM-9361-negative
[RUM-9361] fixing negative values in slow frames, adjusting telemetry
2 parents a608106 + 42fca1b commit 9acf4ba

File tree

10 files changed

+196
-80
lines changed

10 files changed

+196
-80
lines changed

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScope.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ internal open class RumViewScope(
898898
// make a copy - by the time we iterate over it on another thread, it may already be changed
899899
val eventFeatureFlags = featureFlags.toMutableMap()
900900
val eventAdditionalAttributes = (eventAttributes + globalAttributes).toMutableMap()
901-
val uiSlownessReport = slowFramesListener?.resolveReport(viewId, viewComplete)
901+
val uiSlownessReport = slowFramesListener?.resolveReport(viewId, viewComplete, durationNs)
902902
val slowFrames = uiSlownessReport?.slowFramesRecords?.map {
903903
ViewEvent.SlowFrame(
904904
start = it.startTimestampNs - startedNanos,

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/SlowFramesListener.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import kotlin.math.min
1616

1717
internal interface SlowFramesListener : FrameStateListener {
1818
fun onViewCreated(viewId: String, startedTimestampNs: Long)
19-
fun resolveReport(viewId: String, isViewCompleted: Boolean): ViewUIPerformanceReport?
19+
fun resolveReport(viewId: String, isViewCompleted: Boolean, viewDurationNs: Long): ViewUIPerformanceReport?
2020
fun onAddLongTask(durationNs: Long)
2121
}
2222

@@ -41,22 +41,37 @@ internal class DefaultSlowFramesListener(
4141
}
4242

4343
// Called from the main thread
44-
override fun resolveReport(viewId: String, isViewCompleted: Boolean): ViewUIPerformanceReport? {
44+
override fun resolveReport(
45+
viewId: String,
46+
isViewCompleted: Boolean,
47+
viewDurationNs: Long
48+
): ViewUIPerformanceReport? {
4549
@Suppress("UnsafeThirdPartyFunctionCall") // can't have NPE here
4650
val report = if (isViewCompleted) slowFramesRecords.remove(viewId) else slowFramesRecords[viewId]
4751

4852
if (report == null) return null
4953

5054
// making sure that report is not partially updated
5155
return synchronized(report) {
52-
if (isViewCompleted) metricDispatcher.sendMetric(viewId)
56+
if (isViewCompleted) metricDispatcher.sendMetric(viewId, viewDurationNs)
5357
report.copy()
5458
}
5559
}
5660

5761
// Called from the background thread
5862
override fun onFrame(volatileFrameData: FrameData) {
59-
val viewId = currentViewId ?: return
63+
val viewId = currentViewId
64+
if (viewId == null || volatileFrameData.frameStartNanos < currentViewStartedTimeStampNs) {
65+
if (viewId != null) {
66+
metricDispatcher.incrementMissedFrameCount(viewId)
67+
}
68+
// Due to the asynchronous nature of collecting jank frames data, there is a possible situation where
69+
// androidx.performance.metrics started detecting jank frames on a previous view, but reported them after the new view
70+
// was started. In this case, we don't save this data, because the previous view has already
71+
// sent its report, so there is no way to add more data to it, adding a slow frame to the
72+
// current view, would be also wrong, so we just drop such frame data.
73+
return
74+
}
6075
val frameDurationNs = volatileFrameData.frameDurationUiNanos
6176
val frameStartedTimestampNs = volatileFrameData.frameStartNanos
6277
val report = getViewPerformanceReport(viewId)

features/dd-sdk-android-rum/src/main/kotlin/com/datadog/android/rum/internal/metric/slowframes/UISlownessMetricDispatcher.kt

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ internal interface UISlownessMetricDispatcher {
2121

2222
fun incrementIgnoredFrameCount(viewId: String)
2323

24-
fun sendMetric(viewId: String)
24+
fun incrementMissedFrameCount(viewId: String)
25+
26+
fun sendMetric(viewId: String, viewDurationNs: Long)
2527
}
2628

2729
internal class DefaultUISlownessMetricDispatcher(
@@ -32,7 +34,8 @@ internal class DefaultUISlownessMetricDispatcher(
3234

3335
internal data class SlowFramesTelemetry(
3436
val slowFramesCount: AtomicInteger = AtomicInteger(0),
35-
val ignoredFramesCount: AtomicInteger = AtomicInteger(0)
37+
val ignoredFramesCount: AtomicInteger = AtomicInteger(0),
38+
val missedFrameCount: AtomicInteger = AtomicInteger(0)
3639
)
3740

3841
private val viewTelemetry = ConcurrentHashMap<String, SlowFramesTelemetry>()
@@ -52,8 +55,13 @@ internal class DefaultUISlownessMetricDispatcher(
5255
viewTelemetry[viewId]?.ignoredFramesCount?.incrementAndGet()
5356
}
5457

58+
// Called from the background thread
59+
override fun incrementMissedFrameCount(viewId: String) {
60+
viewTelemetry[viewId]?.missedFrameCount?.incrementAndGet()
61+
}
62+
5563
// Called from the main thread
56-
override fun sendMetric(viewId: String) {
64+
override fun sendMetric(viewId: String, viewDurationNs: Long) {
5765
val telemetry = viewTelemetry.remove(viewId)
5866
if (telemetry == null) {
5967
internalLogger.log(
@@ -69,24 +77,30 @@ internal class DefaultUISlownessMetricDispatcher(
6977
messageBuilder = { UI_SLOWNESS_MESSAGE },
7078
additionalProperties = buildMetricAttributesMap(
7179
slowFramesCount = telemetry.slowFramesCount.get(),
72-
ignoredFramesCount = telemetry.ignoredFramesCount.get()
80+
ignoredFramesCount = telemetry.ignoredFramesCount.get(),
81+
missedFramesCount = telemetry.missedFrameCount.get(),
82+
viewDurationNs = viewDurationNs
7383
)
7484
)
7585
}
7686

7787
private fun buildMetricAttributesMap(
7888
slowFramesCount: Int,
79-
ignoredFramesCount: Int
89+
ignoredFramesCount: Int,
90+
missedFramesCount: Int,
91+
viewDurationNs: Long
8092
): Map<String, Any> = buildMap {
8193
put(KEY_METRIC_TYPE, VALUE_METRIC_TYPE)
8294
put(
8395
KEY_RUM_UI_SLOWNESS,
8496
buildMap {
97+
put(KEY_VIEW_DURATION, viewDurationNs)
8598
put(
8699
KEY_SLOW_FRAMES,
87100
buildMap {
88101
put(KEY_COUNT, slowFramesCount)
89102
put(KEY_IGNORED_COUNT, ignoredFramesCount)
103+
put(KEY_MISSED_COUNT, missedFramesCount)
90104
put(
91105
KEY_CONFIG,
92106
buildMap {
@@ -114,7 +128,9 @@ internal class DefaultUISlownessMetricDispatcher(
114128

115129
internal const val KEY_SLOW_FRAMES = "slow_frames"
116130
internal const val KEY_COUNT = "count"
131+
internal const val KEY_VIEW_DURATION = "view_duration"
117132
internal const val KEY_IGNORED_COUNT = "ignored_count"
133+
internal const val KEY_MISSED_COUNT = "missed_count"
118134
internal const val KEY_CONFIG = "config"
119135
internal const val KEY_MAX_COUNT = "max_count"
120136
internal const val KEY_SLOW_FRAME_THRESHOLD = "slow_frame_threshold"

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumApplicationScopeTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ internal class RumApplicationScopeTest {
131131
whenever(mockSdkCore.getFeature(Feature.RUM_FEATURE_NAME)) doReturn mockRumFeatureScope
132132
whenever(mockSdkCore.time) doReturn fakeTimeInfoAtScopeStart
133133
whenever(mockSdkCore.internalLogger) doReturn mock()
134-
whenever(mockSlowFramesListener.resolveReport(any(), any())) doReturn viewUIPerformanceReport
134+
whenever(mockSlowFramesListener.resolveReport(any(), any(), any())) doReturn viewUIPerformanceReport
135135

136136
testedScope = RumApplicationScope(
137137
fakeApplicationId,

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumRawEventExt.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ internal fun Forge.interactiveRumRawEvent(): RumRawEvent {
2626
)
2727
}
2828

29-
internal fun Forge.startViewEvent(): RumRawEvent.StartView {
29+
internal fun Forge.startViewEvent(eventTime: Time = Time()): RumRawEvent.StartView {
3030
return RumRawEvent.StartView(
3131
key = getForgery(),
32-
attributes = exhaustiveAttributes()
32+
attributes = exhaustiveAttributes(),
33+
eventTime = eventTime
3334
)
3435
}
3536

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewManagerScopeTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ internal class RumViewManagerScopeTest {
144144
whenever(mockChildScope.handleEvent(any(), any())) doReturn mockChildScope
145145
whenever(mockChildScope.isActive()) doReturn true
146146
whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger
147-
whenever(mockSlowFramesListener.resolveReport(any(), any())) doReturn fakeViewUIPerformanceReport
147+
whenever(mockSlowFramesListener.resolveReport(any(), any(), any())) doReturn fakeViewUIPerformanceReport
148148

149149
testedScope = RumViewManagerScope(
150150
mockParentScope,

features/dd-sdk-android-rum/src/test/kotlin/com/datadog/android/rum/internal/domain/scope/RumViewScopeTest.kt

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ internal class RumViewScopeTest {
234234
@BoolForgery
235235
var fakeTrackFrustrations: Boolean = true
236236

237+
@LongForgery(min = 1L)
238+
var fakeViewDurationNs: Long = 0
239+
237240
lateinit var fakeReplayStats: ViewEvent.ReplayStats
238241

239242
private var fakeSampleRate: Float = 0.0f
@@ -326,7 +329,7 @@ internal class RumViewScopeTest {
326329
}
327330
.toEvictingQueue()
328331

329-
whenever(mockSlowFramesListener.resolveReport(any(), any())) doReturn mockViewUIPerformanceReport
332+
whenever(mockSlowFramesListener.resolveReport(any(), any(), any())) doReturn mockViewUIPerformanceReport
330333
whenever(mockParentScope.getRumContext()) doReturn fakeParentContext
331334
whenever(mockChildScope.handleEvent(any(), any())) doReturn mockChildScope
332335
whenever(mockActionScope.handleEvent(any(), any())) doReturn mockActionScope
@@ -9626,33 +9629,43 @@ internal class RumViewScopeTest {
96269629
forge: Forge
96279630
) {
96289631
// Given
9629-
fakeEvent = RumRawEvent.StopView(key = testedScope.key, forge.exhaustiveAttributes())
9632+
fakeEvent = RumRawEvent.StopView(
9633+
key = testedScope.key,
9634+
attributes = forge.exhaustiveAttributes(),
9635+
eventTime = Time(nanoTime = fakeEventTime.nanoTime + fakeViewDurationNs)
9636+
)
96309637

96319638
// When
96329639
testedScope.handleEvent(fakeEvent, mockWriter)
96339640

96349641
// Then
9635-
verify(mockSlowFramesListener).resolveReport(testedScope.viewId, true)
9642+
verify(mockSlowFramesListener).resolveReport(testedScope.viewId, true, fakeViewDurationNs)
96369643
}
96379644

96389645
@Test
96399646
fun `M call resolveReport(viewId, true) of slowFramesListener W handleEvent(StartView)`(
96409647
forge: Forge
96419648
) {
96429649
// When
9643-
testedScope.handleEvent(forge.startViewEvent(), mockWriter)
9650+
testedScope.handleEvent(
9651+
forge.startViewEvent(eventTime = Time(nanoTime = fakeEventTime.nanoTime + fakeViewDurationNs)),
9652+
mockWriter
9653+
)
96449654

96459655
// Then
9646-
verify(mockSlowFramesListener).resolveReport(testedScope.viewId, true)
9656+
verify(mockSlowFramesListener).resolveReport(testedScope.viewId, true, fakeViewDurationNs)
96479657
}
96489658

96499659
@Test
9650-
fun `M call resolveReport(viewId, true) of slowFramesListener W handleEvent(StopSession)`() {
9660+
fun `M call resolveReport(viewId, true, Long) of slowFramesListener W handleEvent(StopSession)`() {
96519661
// When
9652-
testedScope.handleEvent(RumRawEvent.StopSession(), mockWriter)
9662+
testedScope.handleEvent(
9663+
RumRawEvent.StopSession(eventTime = Time(nanoTime = fakeEventTime.nanoTime + fakeViewDurationNs)),
9664+
mockWriter
9665+
)
96539666

96549667
// Then
9655-
verify(mockSlowFramesListener).resolveReport(testedScope.viewId, true)
9668+
verify(mockSlowFramesListener).resolveReport(testedScope.viewId, true, fakeViewDurationNs)
96569669
}
96579670

96589671
@Test
@@ -9675,7 +9688,7 @@ internal class RumViewScopeTest {
96759688
testedScope.handleEvent(event, mockWriter)
96769689

96779690
// Then
9678-
verify(mockSlowFramesListener).resolveReport(testedScope.viewId, false)
9691+
verify(mockSlowFramesListener).resolveReport(eq(testedScope.viewId), eq(false), any())
96799692
}
96809693

96819694
// endregion

0 commit comments

Comments
 (0)