Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
491 changes: 381 additions & 110 deletions example/ios/Podfile.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions packages/core/DatadogSDKReactNative.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ Pod::Spec.new do |s|
s.dependency "React-Core"

# /!\ Remember to keep the versions in sync with DatadogSDKReactNativeSessionReplay.podspec
s.dependency 'DatadogCore', '3.11.0'
s.dependency 'DatadogLogs', '3.11.0'
s.dependency 'DatadogTrace', '3.11.0'
s.dependency 'DatadogRUM', '3.11.0'
s.dependency 'DatadogCrashReporting', '3.11.0'
s.dependency 'DatadogFlags', '3.11.0'
s.dependency 'DatadogCore', '3.16.0'
s.dependency 'DatadogLogs', '3.16.0'
s.dependency 'DatadogTrace', '3.16.0'
s.dependency 'DatadogRUM', '3.16.0'
s.dependency 'DatadogCrashReporting', '3.16.0'
s.dependency 'DatadogFlags', '3.16.0'

# DatadogWebViewTracking is not available for tvOS
s.ios.dependency 'DatadogWebViewTracking', '3.11.0'
s.ios.dependency 'DatadogWebViewTracking', '3.16.0'

s.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'ios/Tests/**/*.{swift,json}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ data class DdSdkConfiguration(
* @param initialResourceThreshold The amount of time after a view starts where a Resource should be considered when calculating Time to Network-Settled (TNS).
* @param telemetrySampleRate The sample rate (between 0 and 100) of telemetry events.
* @param customEndpoint Custom RUM intake endpoint used to override the default Datadog intake.
* @param timeseries Experimental: configuration for memory and CPU timeseries collection.
* A non-null value enables timeseries collection.
*/
data class RumConfiguration(
val applicationId: String,
Expand All @@ -82,7 +84,19 @@ data class RumConfiguration(
val trackNonFatalAnrs: Boolean? = null,
val initialResourceThreshold: Double? = null,
val telemetrySampleRate: Double? = null,
val customEndpoint: String? = null
val customEndpoint: String? = null,
val timeseries: TimeseriesConfiguration? = null
)

/**
* Experimental: configuration for memory and CPU timeseries collection.
* Requires a native SDK build with timeseries support.
*
* @param collectTypes The types of timeseries data to collect ("cpu", "memory").
* Defaults to collecting all supported types when omitted.
*/
data class TimeseriesConfiguration(
val collectTypes: List<String>? = null
)

/**
Expand Down Expand Up @@ -157,7 +171,12 @@ internal data class JSONRumConfiguration(
val vitalsUpdateFrequency: String? = null,
val trackMemoryWarnings: Boolean? = null,
val telemetrySampleRate: Double? = null,
val customEndpoint: String? = null
val customEndpoint: String? = null,
val unstable_timeseries: JSONTimeseriesConfiguration? = null
)

internal data class JSONTimeseriesConfiguration(
val collectTypes: List<String>? = null
)

internal data class JSONLogsConfiguration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ internal fun ReadableMap.asDdSdkConfiguration(): DdSdkConfiguration {
trackNonFatalAnrs = rm.getBooleanOrNull("trackNonFatalAnrs"),
initialResourceThreshold = rm.getDoubleOrNull("initialResourceThreshold"),
telemetrySampleRate = rm.getDoubleOrNull("telemetrySampleRate"),
customEndpoint = rm.getString("customEndpoint")
customEndpoint = rm.getString("customEndpoint"),
timeseries = rm.getMap("unstable_timeseries")?.asTimeseriesConfiguration()
)
}

Expand Down Expand Up @@ -86,6 +87,12 @@ internal fun ReadableMap.asDdSdkConfiguration(): DdSdkConfiguration {
}


internal fun ReadableMap.asTimeseriesConfiguration(): TimeseriesConfiguration {
return TimeseriesConfiguration(
collectTypes = getArray("collectTypes")?.toArrayList()?.filterIsInstance<String>()
)
}

internal fun ReadableMap.asConfigurationForTelemetry(): ConfigurationForTelemetry {
return ConfigurationForTelemetry(
initializationType = getString("initializationType"),
Expand Down Expand Up @@ -187,7 +194,8 @@ internal fun JSONDdSdkConfiguration.asDdSdkConfiguration(): DdSdkConfiguration {
initialResourceThreshold = rum.initialResourceThreshold ?: DefaultConfiguration.initialResourceThreshold,
telemetrySampleRate = rum.telemetrySampleRate?.toDouble()
?: DefaultConfiguration.telemetrySampleRate,
customEndpoint = rum.customEndpoint
customEndpoint = rum.customEndpoint,
timeseries = rum.unstable_timeseries?.asTimeseriesConfiguration()
)
}

Expand Down Expand Up @@ -229,6 +237,12 @@ internal fun JSONDdSdkConfiguration.asDdSdkConfiguration(): DdSdkConfiguration {
}


internal fun JSONTimeseriesConfiguration.asTimeseriesConfiguration(): TimeseriesConfiguration {
return TimeseriesConfiguration(
collectTypes = this.collectTypes
)
}

internal fun JSONProxyConfiguration.asProxyConfig(): Pair<Proxy, ProxyAuthenticator?>? {
return buildProxyConfig(type, address, port, username, password)
}
Expand Down Expand Up @@ -331,6 +345,15 @@ internal fun DdSdkConfiguration.toReadableMap(): ReadableMap {
rum.initialResourceThreshold?.let { rumMap.putDouble("initialResourceThreshold", it) }
rum.telemetrySampleRate?.let { rumMap.putDouble("telemetrySampleRate", it) }
rum.customEndpoint?.let { rumMap.putString("customEndpoint", it) }
rum.timeseries?.let { timeseries ->
val timeseriesMap = WritableNativeMap()
timeseries.collectTypes?.let { types ->
val typesArray = WritableNativeArray()
types.forEach { typesArray.pushString(it) }
timeseriesMap.putArray("collectTypes", typesArray)
}
rumMap.putMap("unstable_timeseries", timeseriesMap)
}

map.putMap("rumConfiguration", rumMap)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.datadog.android.event.EventMapper
import com.datadog.android.log.Logs
import com.datadog.android.log.LogsConfiguration
import com.datadog.android.privacy.TrackingConsent
import com.datadog.android.rum.ExperimentalRumApi
import com.datadog.android.rum.Rum
import com.datadog.android.rum.RumConfiguration
import com.datadog.android._InternalProxy
Expand All @@ -27,6 +28,8 @@ import com.datadog.android.rum.configuration.VitalsUpdateFrequency
import com.datadog.android.rum.metric.networksettled.TimeBasedInitialResourceIdentifier
import com.datadog.android.rum.model.ActionEvent
import com.datadog.android.rum.model.ResourceEvent
import com.datadog.android.rum.timeseries.TimeseriesConfiguration as NativeTimeseriesConfiguration
import com.datadog.android.rum.timeseries.TimeseriesType as NativeTimeseriesType
import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy
import com.datadog.android.telemetry.model.TelemetryConfigurationEvent
import com.datadog.android.trace.Trace
Expand Down Expand Up @@ -254,11 +257,37 @@ class DdSdkNativeInitialization internal constructor(
configBuilder.setInitialResourceIdentifier(TimeBasedInitialResourceIdentifier(milliseconds))
}

configuration.rumConfiguration?.timeseries?.let { timeseries ->
setTimeseriesConfiguration(configBuilder, timeseries)
}

configBuilder.setSessionListener(DdSdkSessionStartedListener.getInstance())

return configBuilder.build()
}

@OptIn(ExperimentalRumApi::class)
private fun setTimeseriesConfiguration(
configBuilder: RumConfiguration.Builder,
timeseries: TimeseriesConfiguration
) {
val nativeConfigBuilder = NativeTimeseriesConfiguration.Builder()
val collectTypes = timeseries.collectTypes?.mapNotNull { it.asTimeseriesType() }
if (collectTypes != null) {
nativeConfigBuilder.collectOnly(*collectTypes.toTypedArray())
}

configBuilder.setTimeseriesConfiguration(nativeConfigBuilder.build())
}

private fun String.asTimeseriesType(): NativeTimeseriesType? {
return when (lowercase(Locale.US)) {
"cpu" -> NativeTimeseriesType.CPU
"memory" -> NativeTimeseriesType.MEMORY
else -> null
}
}

private fun buildLogsConfiguration(configuration: DdSdkConfiguration): LogsConfiguration {
val configBuilder = LogsConfiguration.Builder()
configuration.logsConfiguration?.customEndpoint?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ internal class DdSdkNativeInitializationTest {
"[B3MULTI, TRACECONTEXT]"
)
assertThat(configuration.rumConfiguration?.initialResourceThreshold).isEqualTo(0.5)
assertThat(configuration.rumConfiguration?.timeseries?.collectTypes).isEqualTo(
listOf("cpu")
)
}

@Test
Expand Down Expand Up @@ -192,6 +195,7 @@ internal class DdSdkNativeInitializationTest {
assertThat(configuration.rumConfiguration?.firstPartyHosts).isNull()
assertThat(configuration.logsConfiguration?.customEndpoint).isNull()
assertThat(configuration.traceConfiguration?.customEndpoint).isNull()
assertThat(configuration.rumConfiguration?.timeseries).isNull()
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.datadog.android.rum.configuration.VitalsUpdateFrequency
import com.datadog.android.rum.metric.networksettled.TimeBasedInitialResourceIdentifier
import com.datadog.android.rum.model.ActionEvent
import com.datadog.android.rum.model.ResourceEvent
import com.datadog.android.rum.timeseries.TimeseriesType
import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy
import com.datadog.android.telemetry.model.TelemetryConfigurationEvent
import com.datadog.android.trace.Trace
Expand Down Expand Up @@ -2368,6 +2369,106 @@ internal class DdSdkTest {
}
}

@Test
fun `𝕄 initialize native SDK 𝕎 initialize() {timeseries enabled}`(
@Forgery configuration: DdSdkConfiguration
) {
// Given
val rumConfiguration = configuration.rumConfiguration?.copy(
timeseries = TimeseriesConfiguration(collectTypes = listOf("cpu"))
)
val bridgeConfiguration = configuration.copy(
rumConfiguration = rumConfiguration
)
val sdkConfigCaptor = argumentCaptor<Configuration>()
val rumConfigCaptor = argumentCaptor<RumConfiguration>()
val logsConfigCaptor = argumentCaptor<LogsConfiguration>()
val traceConfigCaptor = argumentCaptor<TraceConfiguration>()

val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java)
val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java)
val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java)

try {
rumMock.`when`<Unit> { Rum.enable(any(), any()) }.then { }
logsMock.`when`<Unit> { Logs.enable(any(), any()) }.then { }
traceMock.`when`<Unit> { Trace.enable(any(), any()) }.then { }

// When
testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise)

// Then
inOrder(mockDatadog) {
verify(mockDatadog).initialize(
same(mockContext),
sdkConfigCaptor.capture(),
any()
)
rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) }
traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) }
logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) }
}
assertThat(rumConfigCaptor.firstValue)
.hasField("featureConfiguration") {
it.hasField("timeseriesConfiguration") { ts ->
ts.hasFieldEqualTo("enabledTypes", setOf(TimeseriesType.CPU))
}
}
} finally {
rumMock.close()
logsMock.close()
traceMock.close()
}
}

@Test
fun `𝕄 initialize native SDK 𝕎 initialize() {timeseries disabled by default}`(
@Forgery configuration: DdSdkConfiguration
) {
// Given
val rumConfiguration = configuration.rumConfiguration?.copy(timeseries = null)
val bridgeConfiguration = configuration.copy(
rumConfiguration = rumConfiguration
)
val sdkConfigCaptor = argumentCaptor<Configuration>()
val rumConfigCaptor = argumentCaptor<RumConfiguration>()
val logsConfigCaptor = argumentCaptor<LogsConfiguration>()
val traceConfigCaptor = argumentCaptor<TraceConfiguration>()

val rumMock = org.mockito.Mockito.mockStatic(Rum::class.java)
val traceMock = org.mockito.Mockito.mockStatic(Trace::class.java)
val logsMock = org.mockito.Mockito.mockStatic(Logs::class.java)

try {
rumMock.`when`<Unit> { Rum.enable(any(), any()) }.then { }
logsMock.`when`<Unit> { Logs.enable(any(), any()) }.then { }
traceMock.`when`<Unit> { Trace.enable(any(), any()) }.then { }

// When
testedBridgeSdk.initialize(bridgeConfiguration.toReadableJavaOnlyMap(), mockPromise)

// Then
inOrder(mockDatadog) {
verify(mockDatadog).initialize(
same(mockContext),
sdkConfigCaptor.capture(),
any()
)
rumMock.verify { Rum.enable(rumConfigCaptor.capture(), any()) }
traceMock.verify { Trace.enable(traceConfigCaptor.capture(), any()) }
logsMock.verify { Logs.enable(logsConfigCaptor.capture(), any()) }
}
assertThat(rumConfigCaptor.firstValue)
.hasField("featureConfiguration") {
it.hasFieldEqualTo("timeseriesConfiguration", null)
}
} finally {
rumMock.close()
logsMock.close()
traceMock.close()
}
}

@Test
fun `𝕄 initialize native SDK 𝕎 initialize() {never vitals frequency update}`(
@Forgery configuration: DdSdkConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.datadog.android.rum.configuration.VitalsUpdateFrequency
import com.datadog.reactnative.ConfigurationForTelemetry
import com.datadog.reactnative.DdSdkConfiguration
import com.datadog.reactnative.ProxyAuthenticator
import com.datadog.reactnative.TimeseriesConfiguration
import com.facebook.react.bridge.ReadableMap
import java.net.Proxy

Expand Down Expand Up @@ -67,6 +68,7 @@ fun DdSdkConfiguration.toReadableJavaOnlyMap(): ReadableMap {
rumMap["initialResourceThreshold"] = rum?.initialResourceThreshold ?: 0.1
rumMap["telemetrySampleRate"] = rum?.telemetrySampleRate ?: 20.0
rum?.customEndpoint?.let { rumMap["customEndpoint"] = it }
rum?.timeseries?.let { rumMap["unstable_timeseries"] = it.toReadableMap() }
map["rumConfiguration"] = rumMap.toReadableMap()
}

Expand All @@ -93,6 +95,12 @@ fun DdSdkConfiguration.toReadableJavaOnlyMap(): ReadableMap {
return map.toReadableMap()
}

internal fun TimeseriesConfiguration.toReadableMap(): ReadableMap {
val map = mutableMapOf<String, Any?>()
collectTypes?.let { map["collectTypes"] = it.toReadableArray() }
return map.toReadableMap()
}

internal fun ConfigurationForTelemetry.toReadableJavaOnlyMap(): ReadableMap {
val map = mutableMapOf<String, Any?>()
initializationType?.let { map.put("initializationType", it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
"resourceTraceSampleRate": 33,
"trackBackgroundEvents": true,
"trackFrustrations": false,
"unstable_timeseries": {
"collectTypes": [
"cpu"
]
},
"firstPartyHosts": [
{
"match": "example.com",
Expand Down
18 changes: 18 additions & 0 deletions packages/core/datadog-configuration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,24 @@
"customEndpoint": {
"description": "Target a custom server for RUM.",
"type": "string"
},
"unstable_timeseries": {
"description": "Experimental: configuration for memory and CPU timeseries collection. Requires a native SDK build with timeseries support. Presence of this object enables timeseries collection.",
"type": "object",
"properties": {
"collectTypes": {
"description": "The types of timeseries data to collect. Defaults to collecting all supported types when omitted.",
"type": "array",
"items": {
"description": "Type of timeseries data to collect.",
"type": "string",
"enum": [
"cpu",
"memory"
]
}
}
}
}
},
"required": [
Expand Down
Loading