Skip to content

Commit 4d02ee3

Browse files
committed
RUM-15138: Read session sample rate from span tags instead of provider-based plumbing
Replace the multi-layer sessionSampleRateProvider chain (FeatureContextUpdateReceiver → TracingFeature → AtomicReference → DeterministicTraceSampler) with direct reads from the session_sample_rate span tag that RumContextPropagator already writes before sampling runs. This eliminates SessionSampleRateRegistrationEvent, FeatureContextUpdateReceiver on TracingFeature, AtomicReference caching, and sendEvent cross-module wiring — resolving locking concerns, timing races, and misleading defaults flagged during review. getSampleRate() (no-arg) now returns the raw configured trace rate. Per-span rebasing lives in sample(item) and the deprecated getSampleRate(item), accessed through the effectiveSampleRate() casting bridge until V4 allows replacing the Sampler<DatadogSpan> interface.
1 parent a985cb8 commit 4d02ee3

14 files changed

Lines changed: 119 additions & 455 deletions

File tree

detekt_custom_safe_calls.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,6 @@ datadog:
597597
- "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.FlagsClientState?)"
598598
- "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.flags.model.ProviderContext?)"
599599
- "java.util.concurrent.atomic.AtomicReference.constructor(com.datadog.android.rum.internal.domain.RumContext?)"
600-
- "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.Float?)"
601600
- "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Map?)"
602601
- "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.String?)"
603602
- "java.util.concurrent.atomic.AtomicReference.constructor(kotlin.collections.Set?)"
@@ -614,7 +613,6 @@ datadog:
614613
- "java.util.concurrent.atomic.AtomicReference.set(com.datadog.android.flags.model.ProviderContext?)"
615614
- "java.util.concurrent.atomic.AtomicReference.set(com.datadog.trace.bootstrap.instrumentation.api.AgentTracer.TracerAPI?)"
616615
- "java.util.concurrent.atomic.AtomicReference.set(com.datadog.trace.core.CoreTracer?)"
617-
- "java.util.concurrent.atomic.AtomicReference.set(kotlin.Float?)"
618616
- "java.util.concurrent.atomic.AtomicReference.set(kotlin.collections.Map?)"
619617
- "java.util.concurrent.atomic.AtomicReference.set(kotlin.Nothing?)"
620618
- "java.util.concurrent.atomic.AtomicReference.set(kotlin.String?)"
@@ -999,7 +997,6 @@ datadog:
999997
- "kotlin.collections.MutableSet.add(com.datadog.android.sessionreplay.ExtensionSupport)"
1000998
- "kotlin.collections.MutableSet.add(com.datadog.android.telemetry.internal.TelemetryEventId)"
1001999
- "kotlin.collections.MutableSet.add(java.io.File)"
1002-
- "kotlin.collections.MutableSet.add(java.util.concurrent.atomic.AtomicReference)"
10031000
- "kotlin.collections.MutableSet.add(kotlin.String)"
10041001
- "kotlin.collections.MutableSet.add(kotlin.String?)"
10051002
- "kotlin.collections.MutableSet.addAll(kotlin.collections.Collection)"

features/dd-sdk-android-trace/api/apiSurface

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ open class com.datadog.android.trace.DeterministicTraceSampler : com.datadog.and
2222
constructor(() -> Float)
2323
constructor(Float)
2424
constructor(Double)
25-
override fun getSampleRate(): Float
2625
override fun sample(com.datadog.android.trace.api.span.DatadogSpan): Boolean
2726
DEPRECATED fun getSampleRate(com.datadog.android.trace.api.span.DatadogSpan): Float
2827
annotation com.datadog.android.trace.ExperimentalTraceApi

features/dd-sdk-android-trace/api/dd-sdk-android-trace.api

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public class com/datadog/android/trace/DeterministicTraceSampler : com/datadog/a
3434
public fun <init> (D)V
3535
public fun <init> (F)V
3636
public fun <init> (Lkotlin/jvm/functions/Function0;)V
37-
public fun getSampleRate ()Ljava/lang/Float;
3837
public final fun getSampleRate (Lcom/datadog/android/trace/api/span/DatadogSpan;)F
3938
public fun sample (Lcom/datadog/android/trace/api/span/DatadogSpan;)Z
4039
public synthetic fun sample (Ljava/lang/Object;)Z

features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/ApmNetworkInstrumentationConfiguration.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ import com.datadog.android.core.sampling.Sampler
1313
import com.datadog.android.trace.api.span.DatadogSpan
1414
import com.datadog.android.trace.api.tracer.DatadogTracer
1515
import com.datadog.android.trace.internal.ApmNetworkInstrumentation
16-
import com.datadog.android.trace.internal.TracingFeature
1716
import com.datadog.android.trace.internal.net.TracerProvider
18-
import java.util.concurrent.atomic.AtomicReference
1917

2018
/**
2119
* Configuration for APM distributed tracing of network requests.
@@ -251,12 +249,8 @@ class ApmNetworkInstrumentationConfiguration internal constructor(
251249

252250
val tracerProvider = TracerProvider(localTracerFactory, globalTracerProvider)
253251

254-
@Suppress("UnsafeThirdPartyFunctionCall") // AtomicReference initialized with non-null Float constant
255-
val cachedSessionSampleRate = AtomicReference(TracingFeature.NO_SESSION_REBASING_RATE)
256-
val sessionSampleRateRef: AtomicReference<Float>? =
257-
if (customTraceSampler == null) cachedSessionSampleRate else null
258252
val effectiveSampler = customTraceSampler
259-
?: DeterministicTraceSampler(traceSampleRate) { cachedSessionSampleRate.get() }
253+
?: DeterministicTraceSampler(traceSampleRate)
260254

261255
return ApmNetworkInstrumentation(
262256
canSendSpan = !headerPropagationOnly,
@@ -269,8 +263,7 @@ class ApmNetworkInstrumentationConfiguration internal constructor(
269263
networkingLibraryName = instrumentationName,
270264
tracedRequestListener = tracedRequestListener,
271265
redacted404ResourceName = redacted404ResourceName,
272-
localFirstPartyHostHeaderTypeResolver = localFirstPartyHostHeaderTypeResolver,
273-
sessionSampleRateRef = sessionSampleRateRef
266+
localFirstPartyHostHeaderTypeResolver = localFirstPartyHostHeaderTypeResolver
274267
)
275268
}
276269

features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/DeterministicTraceSampler.kt

Lines changed: 31 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,31 @@ import com.datadog.android.trace.api.span.DatadogSpan
1414
import com.datadog.android.trace.internal.net.SpanSamplingIdProvider
1515

1616
/**
17-
* A [com.datadog.android.core.sampling.DeterministicSampler] using the TraceID of a Span to compute the sampling decision.
17+
* A [com.datadog.android.core.sampling.DeterministicSampler] using the TraceID of a Span to compute the sampling
18+
* decision.
1819
*
19-
* The sampling threshold is automatically rebased against the RUM session sample rate when it is
20-
* available so that the combined effective sampling probability reflects:
20+
* When a span carries a RUM session context (written by
21+
* [com.datadog.android.trace.internal.RumContextPropagator]), the sampling threshold is
22+
* automatically rebased against the RUM session sample rate so that the combined effective
23+
* sampling probability reflects:
2124
*
2225
* ```
23-
* rebasedTraceSampleRate = sessionSampleRate * traceSampleRate / 100
26+
* effectiveRate = traceSampleRate * sessionSampleRate / 100
2427
* ```
2528
*
26-
* Note: overriding [getSampleRate] is not a supported extension point for per-span sampling
27-
* behavior.
29+
* Spans without a `session_sample_rate` tag are sampled at the raw trace sample rate.
2830
*
2931
* @param sampleRateProvider Provider for the trace sample rate. Called each time the sampling
3032
* decision needs to be made. Values must be in the range [0;100].
31-
* @param sessionSampleRateProvider Provider for the RUM session sample rate used to rebase the
32-
* trace sample rate. Defaults to 100 (no rebasing).
3333
*/
3434
// TODO RUM-13454 -> Make this class internal in V4 (RUM-15590)
35-
open class DeterministicTraceSampler private constructor(
36-
private val sampleRateProvider: () -> Float,
37-
private val sessionSampleRateProvider: () -> Float
35+
open class DeterministicTraceSampler(
36+
sampleRateProvider: () -> Float
3837
) : DeterministicSampler<DatadogSpan>(
3938
SpanSamplingIdProvider::provideId,
4039
sampleRateProvider
4140
) {
4241

43-
/**
44-
* Creates a new instance of [DeterministicTraceSampler] with the given sample rate provider.
45-
*
46-
* @param sampleRateProvider Provider for the sample rate value.
47-
*/
48-
constructor(
49-
sampleRateProvider: () -> Float
50-
) : this(sampleRateProvider, { DeterministicSampler.SAMPLE_ALL_RATE })
51-
5242
/**
5343
* Creates a new instance of [DeterministicTraceSampler] with the given sample rate.
5444
*
@@ -67,77 +57,47 @@ open class DeterministicTraceSampler private constructor(
6757
@FloatRange(from = 0.0, to = 100.0) sampleRate: Double
6858
) : this(sampleRate.toFloat())
6959

70-
/**
71-
* Internal constructor used by [com.datadog.android.trace.ApmNetworkInstrumentationConfiguration]
72-
* to wire the RUM session sample rate provider at instrumentation setup time.
73-
*/
74-
internal constructor(
75-
@FloatRange(from = 0.0, to = 100.0) sampleRate: Float,
76-
sessionSampleRateProvider: () -> Float
77-
) : this({ sampleRate }, sessionSampleRateProvider)
78-
79-
/**
80-
* Returns the effective sample rate, rebased against the RUM session sample rate when it is
81-
* available:
82-
*
83-
* ```
84-
* effectiveRate = traceSampleRate * sessionSampleRate / 100
85-
* ```
86-
*
87-
* Falls back to the raw trace sample rate when no session sample rate is available
88-
* (i.e. [sessionSampleRateProvider] returns 100).
89-
*/
90-
override fun getSampleRate(): Float {
91-
val traceSampleRate = super.getSampleRate()
92-
val sessionSampleRate = sessionSampleRateProvider()
93-
return if (sessionSampleRate >= 0f && sessionSampleRate < DeterministicSampler.SAMPLE_ALL_RATE) {
94-
val rebased = traceSampleRate * sessionSampleRate / DeterministicSampler.SAMPLE_ALL_RATE
95-
rebased.coerceAtMost(DeterministicSampler.SAMPLE_ALL_RATE)
96-
} else {
97-
traceSampleRate
98-
}
99-
}
100-
10160
/** @inheritDoc */
10261
// TODO RUM-13454 -> Remove the @Suppress when getSampleRate(DatadogSpan) is removed in V4 (RUM-15590)
10362
@Suppress("DEPRECATION")
10463
override fun sample(item: DatadogSpan): Boolean =
10564
computeSamplingDecision(getSampleRate(item), SpanSamplingIdProvider.provideId(item))
10665

10766
/**
108-
* Returns the effective sample rate for [item], applying cross-product rebasing when a RUM
109-
* session rate is available.
67+
* Returns the effective sample rate for [item], applying cross-product rebasing when the
68+
* span carries a `session_sample_rate` tag (written by
69+
* [com.datadog.android.trace.internal.RumContextPropagator]).
11070
*
111-
* When this sampler was created via [ApmNetworkInstrumentationConfiguration] the session rate
112-
* is read from the injected [sessionSampleRateProvider]. For samplers created via the public
113-
* constructors (no provider) the rate is read from the `session_sample_rate` span tag written
114-
* by [com.datadog.android.trace.internal.RumContextPropagator], providing compatibility with
115-
* [com.datadog.android.okhttp.trace.TracingInterceptor] and related legacy integrations.
71+
* When no tag is present the raw trace sample rate is returned unchanged.
11672
*/
11773
// TODO RUM-13454 -> Remove this in V4 when deprecating legacy paths (RUM-15590)
11874
@Deprecated(
119-
"Use ApmNetworkInstrumentationConfiguration for session-aware sampling. Will be removed in v4.",
75+
"Will be removed in v4.",
12076
level = DeprecationLevel.WARNING
12177
)
12278
fun getSampleRate(item: DatadogSpan): Float {
123-
// Compatibility path for integrations creating this sampler via public constructors
124-
// (no sessionSampleRateProvider). In that case the session sample rate is propagated on
125-
// the span context as `session_sample_rate` and must be used for rebasing.
126-
val providerRate = sessionSampleRateProvider()
127-
val providerRateIsValid = providerRate >= 0f && providerRate < DeterministicSampler.SAMPLE_ALL_RATE
128-
val sessionSampleRate = when {
129-
providerRateIsValid -> providerRate
130-
else -> (item.context().tags[LogAttributes.RUM_SESSION_SAMPLE_RATE] as? Number)?.toFloat()
131-
}
79+
return resolveEffectiveSampleRate(item)
80+
}
13281

82+
// region private
83+
84+
private fun resolveEffectiveSampleRate(item: DatadogSpan): Float {
85+
val traceSampleRate = super.getSampleRate()
86+
val sessionSampleRate = resolveSessionSampleRate(item)
13387
return if (sessionSampleRate != null &&
13488
sessionSampleRate >= 0f &&
13589
sessionSampleRate < DeterministicSampler.SAMPLE_ALL_RATE
13690
) {
137-
val rebased = super.getSampleRate() * sessionSampleRate / DeterministicSampler.SAMPLE_ALL_RATE
138-
if (rebased > DeterministicSampler.SAMPLE_ALL_RATE) DeterministicSampler.SAMPLE_ALL_RATE else rebased
91+
(traceSampleRate * sessionSampleRate / DeterministicSampler.SAMPLE_ALL_RATE)
92+
.coerceAtMost(DeterministicSampler.SAMPLE_ALL_RATE)
13993
} else {
140-
super.getSampleRate()
94+
traceSampleRate
14195
}
14296
}
97+
98+
private fun resolveSessionSampleRate(item: DatadogSpan): Float? {
99+
return (item.context().tags[LogAttributes.RUM_SESSION_SAMPLE_RATE] as? Number)?.toFloat()
100+
}
101+
102+
// endregion
143103
}

features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/ApmNetworkInstrumentation.kt

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package com.datadog.android.trace.internal
88

99
import com.datadog.android.api.InternalLogger
1010
import com.datadog.android.api.SdkCore
11-
import com.datadog.android.api.feature.Feature
1211
import com.datadog.android.api.instrumentation.network.HttpRequestInfo
1312
import com.datadog.android.api.instrumentation.network.HttpRequestInfoBuilder
1413
import com.datadog.android.api.instrumentation.network.HttpResponseInfo
@@ -38,7 +37,6 @@ import com.datadog.android.trace.internal.net.finishRumAware
3837
import com.datadog.android.trace.internal.net.sample
3938
import java.net.HttpURLConnection
4039
import java.util.Locale
41-
import java.util.concurrent.atomic.AtomicReference
4240

4341
/**
4442
* For internal usage only.
@@ -59,11 +57,6 @@ import java.util.concurrent.atomic.AtomicReference
5957
* @param localFirstPartyHostHeaderTypeResolver resolver for determining header types for first-party hosts.
6058
* @param networkingLibraryName the name identifying the network instrumentation (e.g., "OkHttp", "Cronet").
6159
* @param networkTracingScope Tracing scope for the instrumentation. See [ApmNetworkTracingScope] enum for more details.
62-
* @param sessionSampleRateRef optional [AtomicReference] that [TracingFeature] will keep
63-
* up-to-date with the latest RUM session sample rate. When the SDK core resolves,
64-
* this ref is sent to [TracingFeature] via `sendEvent` so that it is updated whenever
65-
* the RUM context changes. The corresponding [DeterministicTraceSampler] reads from
66-
* this ref on every request (volatile read, no locking).
6760
*/
6861
@Suppress("LongParameterList")
6962
@InternalApi
@@ -78,8 +71,7 @@ class ApmNetworkInstrumentation internal constructor(
7871
internal val tracedRequestListener: NetworkTracedRequestListener,
7972
internal val localFirstPartyHostHeaderTypeResolver: DefaultFirstPartyHostHeaderTypeResolver,
8073
private val networkingLibraryName: String,
81-
val networkTracingScope: ApmNetworkTracingScope = ApmNetworkTracingScope.ALL,
82-
internal val sessionSampleRateRef: AtomicReference<Float>? = null
74+
val networkTracingScope: ApmNetworkTracingScope = ApmNetworkTracingScope.ALL
8375
) {
8476
private val rumContextPropagator = RumContextPropagator { internalSdkCore }
8577

@@ -98,10 +90,6 @@ class ApmNetworkInstrumentation internal constructor(
9890
WARNING_TRACING_NO_HOSTS.format(Locale.US, networkingLibraryName)
9991
}
10092
}
101-
sessionSampleRateRef?.let { ref ->
102-
sdkCore.getFeature(Feature.TRACING_FEATURE_NAME)
103-
?.sendEvent(SessionSampleRateRegistrationEvent(ref))
104-
}
10593
}
10694

10795
/** The sample rate used by the trace sampler, or null if unavailable. */

features/dd-sdk-android-trace/src/main/kotlin/com/datadog/android/trace/internal/SessionSampleRateRegistrationEvent.kt

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)