Skip to content

Commit e9411c6

Browse files
committed
RUM-8041: Session Ended upload quality telemetry
1 parent c0044da commit e9411c6

File tree

18 files changed

+856
-94
lines changed

18 files changed

+856
-94
lines changed

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/DatadogCore.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,10 @@ internal class DatadogCore(
128128
/** @inheritDoc */
129129
override fun registerFeature(feature: Feature) {
130130
val sdkFeature = SdkFeature(
131-
coreFeature,
132-
feature,
133-
internalLogger
131+
featureSdkCore = this,
132+
coreFeature = coreFeature,
133+
wrappedFeature = feature,
134+
internalLogger = internalLogger
134135
)
135136
features[feature.name] = sdkFeature
136137
sdkFeature.initialize(context, instanceId)

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/SdkFeature.kt

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.datadog.android.api.feature.Feature
1616
import com.datadog.android.api.feature.FeatureContextUpdateReceiver
1717
import com.datadog.android.api.feature.FeatureEventReceiver
1818
import com.datadog.android.api.feature.FeatureScope
19+
import com.datadog.android.api.feature.FeatureSdkCore
1920
import com.datadog.android.api.feature.StorageBackedFeature
2021
import com.datadog.android.api.net.RequestFactory
2122
import com.datadog.android.api.storage.EventBatchWriter
@@ -63,6 +64,7 @@ import java.util.concurrent.atomic.AtomicReference
6364

6465
@Suppress("TooManyFunctions")
6566
internal class SdkFeature(
67+
private val featureSdkCore: FeatureSdkCore,
6668
internal val coreFeature: CoreFeature,
6769
internal val wrappedFeature: Feature,
6870
internal val internalLogger: InternalLogger
@@ -261,6 +263,7 @@ internal class SdkFeature(
261263
uploadScheduler = if (coreFeature.isMainProcess) {
262264
uploader = createUploader(feature.requestFactory)
263265
DataUploadScheduler(
266+
featureSdkCore,
264267
feature.name,
265268
storage,
266269
uploader,

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/DataUploadRunnable.kt

+63
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import androidx.annotation.WorkerThread
1010
import com.datadog.android.api.InternalLogger
1111
import com.datadog.android.api.context.DatadogContext
1212
import com.datadog.android.api.context.NetworkInfo
13+
import com.datadog.android.api.feature.Feature
14+
import com.datadog.android.api.feature.FeatureSdkCore
1315
import com.datadog.android.api.storage.RawBatchEvent
1416
import com.datadog.android.core.configuration.UploadSchedulerStrategy
1517
import com.datadog.android.core.internal.ContextProvider
@@ -19,10 +21,14 @@ import com.datadog.android.core.internal.persistence.BatchId
1921
import com.datadog.android.core.internal.persistence.Storage
2022
import com.datadog.android.core.internal.system.SystemInfoProvider
2123
import com.datadog.android.core.internal.utils.scheduleSafe
24+
import com.datadog.android.internal.telemetry.UploadQualityBlockers
25+
import com.datadog.android.internal.telemetry.UploadQualityCategories
26+
import com.datadog.android.internal.telemetry.UploadQualityEvent
2227
import java.util.concurrent.ScheduledThreadPoolExecutor
2328
import java.util.concurrent.TimeUnit
2429

2530
internal class DataUploadRunnable(
31+
featureSdkCore: FeatureSdkCore,
2632
private val featureName: String,
2733
private val threadPoolExecutor: ScheduledThreadPoolExecutor,
2834
private val storage: Storage,
@@ -35,12 +41,15 @@ internal class DataUploadRunnable(
3541
private val internalLogger: InternalLogger
3642
) : UploadRunnable {
3743

44+
private val rumFeature = featureSdkCore.getFeature(Feature.RUM_FEATURE_NAME)
45+
3846
// region Runnable
3947

4048
@WorkerThread
4149
override fun run() {
4250
var uploadAttempts = 0
4351
var lastBatchUploadStatus: UploadStatus? = null
52+
4453
if (isNetworkAvailable() && isSystemReady()) {
4554
val context = contextProvider.context
4655
var batchConsumerAvailableAttempts = maxBatchesPerJob
@@ -55,6 +64,8 @@ internal class DataUploadRunnable(
5564
)
5665
}
5766

67+
logUploadQualityEvents()
68+
5869
val delayMs = uploadSchedulerStrategy.getMsDelayUntilNextUpload(
5970
featureName,
6071
uploadAttempts,
@@ -84,11 +95,46 @@ internal class DataUploadRunnable(
8495
return uploadStatus
8596
}
8697

98+
private fun logUploadQualityEvents() {
99+
sendUploadQualityEvent(category = UploadQualityCategories.COUNT)
100+
101+
if (!isNetworkAvailable()) {
102+
sendUploadQualityEvent(
103+
category = UploadQualityCategories.BLOCKER,
104+
specificType = UploadQualityBlockers.OFFLINE.key
105+
)
106+
}
107+
108+
if (isLowPower()) {
109+
sendUploadQualityEvent(
110+
category = UploadQualityCategories.BLOCKER,
111+
specificType = UploadQualityBlockers.LOW_BATTERY.key
112+
)
113+
}
114+
115+
if (isPowerSaveMode()) {
116+
sendUploadQualityEvent(
117+
category = UploadQualityCategories.BLOCKER,
118+
specificType = UploadQualityBlockers.POWER_SAVE_MODE.key
119+
)
120+
}
121+
}
122+
87123
private fun isNetworkAvailable(): Boolean {
88124
val networkInfo = networkInfoProvider.getLatestNetworkInfo()
89125
return networkInfo.connectivity != NetworkInfo.Connectivity.NETWORK_NOT_CONNECTED
90126
}
91127

128+
private fun isPowerSaveMode(): Boolean {
129+
val systemInfo = systemInfoProvider.getLatestSystemInfo()
130+
return systemInfo.powerSaveMode
131+
}
132+
133+
private fun isLowPower(): Boolean {
134+
val systemInfo = systemInfoProvider.getLatestSystemInfo()
135+
return systemInfo.batteryLevel <= LOW_BATTERY_THRESHOLD
136+
}
137+
92138
private fun isSystemReady(): Boolean {
93139
val systemInfo = systemInfoProvider.getLatestSystemInfo()
94140
val hasEnoughPower = systemInfo.batteryFullOrCharging ||
@@ -116,6 +162,12 @@ internal class DataUploadRunnable(
116162
batchMeta: ByteArray?
117163
): UploadStatus {
118164
val status = dataUploader.upload(context, batch, batchMeta, batchId)
165+
if (status.code != HTTP_SUCCESS_CODE) {
166+
sendUploadQualityEvent(
167+
category = UploadQualityCategories.FAILURE,
168+
specificType = status.code.toString()
169+
)
170+
}
119171
val removalReason = if (status is UploadStatus.RequestCreationError) {
120172
RemovalReason.Invalid
121173
} else {
@@ -125,9 +177,20 @@ internal class DataUploadRunnable(
125177
return status
126178
}
127179

180+
private fun sendUploadQualityEvent(category: UploadQualityCategories, specificType: String? = null) {
181+
rumFeature?.sendEvent(
182+
UploadQualityEvent(
183+
track = featureName,
184+
category = category,
185+
specificType = specificType
186+
)
187+
)
188+
}
189+
128190
// endregion
129191

130192
companion object {
131193
internal const val LOW_BATTERY_THRESHOLD = 10
194+
private const val HTTP_SUCCESS_CODE = 202
132195
}
133196
}

dd-sdk-android-core/src/main/kotlin/com/datadog/android/core/internal/data/upload/DataUploadScheduler.kt

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package com.datadog.android.core.internal.data.upload
88

99
import com.datadog.android.api.InternalLogger
10+
import com.datadog.android.api.feature.FeatureSdkCore
1011
import com.datadog.android.core.configuration.UploadSchedulerStrategy
1112
import com.datadog.android.core.internal.ContextProvider
1213
import com.datadog.android.core.internal.net.info.NetworkInfoProvider
@@ -16,6 +17,7 @@ import com.datadog.android.core.internal.utils.executeSafe
1617
import java.util.concurrent.ScheduledThreadPoolExecutor
1718

1819
internal class DataUploadScheduler(
20+
featureSdkCore: FeatureSdkCore,
1921
private val featureName: String,
2022
storage: Storage,
2123
dataUploader: DataUploader,
@@ -29,6 +31,7 @@ internal class DataUploadScheduler(
2931
) : UploadScheduler {
3032

3133
internal val runnable = DataUploadRunnable(
34+
featureSdkCore = featureSdkCore,
3235
featureName = featureName,
3336
threadPoolExecutor = scheduledThreadPoolExecutor,
3437
storage = storage,

dd-sdk-android-core/src/test/kotlin/com/datadog/android/core/internal/SdkFeatureTest.kt

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.datadog.android.api.context.DatadogContext
1313
import com.datadog.android.api.feature.Feature
1414
import com.datadog.android.api.feature.FeatureContextUpdateReceiver
1515
import com.datadog.android.api.feature.FeatureEventReceiver
16+
import com.datadog.android.api.feature.FeatureSdkCore
1617
import com.datadog.android.api.feature.StorageBackedFeature
1718
import com.datadog.android.api.storage.EventBatchWriter
1819
import com.datadog.android.api.storage.FeatureStorageConfiguration
@@ -101,6 +102,9 @@ internal class SdkFeatureTest {
101102
@Mock
102103
lateinit var mockInternalLogger: InternalLogger
103104

105+
@Mock
106+
lateinit var mockFeatureSdkCore: FeatureSdkCore
107+
104108
@Forgery
105109
lateinit var fakeConsent: TrackingConsent
106110

@@ -133,6 +137,7 @@ internal class SdkFeatureTest {
133137
whenever(mockWrappedFeature.requestFactory) doReturn mock()
134138
whenever(mockWrappedFeature.storageConfiguration) doReturn fakeStorageConfiguration
135139
testedFeature = SdkFeature(
140+
featureSdkCore = mockFeatureSdkCore,
136141
coreFeature = coreFeature.mockInstance,
137142
wrappedFeature = mockWrappedFeature,
138143
internalLogger = mockInternalLogger
@@ -248,8 +253,9 @@ internal class SdkFeatureTest {
248253
val mockFeature = mock<TrackingConsentFeature>()
249254
whenever(mockFeature.name).thenReturn(fakeFeatureName)
250255
testedFeature = SdkFeature(
251-
coreFeature.mockInstance,
252-
mockFeature,
256+
featureSdkCore = mockFeatureSdkCore,
257+
coreFeature = coreFeature.mockInstance,
258+
wrappedFeature = mockFeature,
253259
internalLogger = mockInternalLogger
254260
)
255261

@@ -268,6 +274,7 @@ internal class SdkFeatureTest {
268274
whenever(name) doReturn fakeFeatureName
269275
}
270276
testedFeature = SdkFeature(
277+
featureSdkCore = mockFeatureSdkCore,
271278
coreFeature = coreFeature.mockInstance,
272279
wrappedFeature = mockSimpleFeature,
273280
internalLogger = mockInternalLogger
@@ -369,6 +376,7 @@ internal class SdkFeatureTest {
369376
whenever(name) doReturn fakeFeatureName
370377
}
371378
testedFeature = SdkFeature(
379+
featureSdkCore = mockFeatureSdkCore,
372380
coreFeature = coreFeature.mockInstance,
373381
wrappedFeature = mockFeature,
374382
internalLogger = mockInternalLogger
@@ -552,6 +560,7 @@ internal class SdkFeatureTest {
552560
val fakeFeature = FakeFeature(fakeFeatureName)
553561

554562
testedFeature = SdkFeature(
563+
featureSdkCore = mockFeatureSdkCore,
555564
coreFeature = coreFeature.mockInstance,
556565
wrappedFeature = fakeFeature,
557566
internalLogger = mockInternalLogger
@@ -572,6 +581,7 @@ internal class SdkFeatureTest {
572581
val fakeFeature = FakeFeature(fakeFeatureName)
573582

574583
testedFeature = SdkFeature(
584+
featureSdkCore = mockFeatureSdkCore,
575585
coreFeature = coreFeature.mockInstance,
576586
wrappedFeature = fakeFeature,
577587
internalLogger = mockInternalLogger

0 commit comments

Comments
 (0)