diff --git a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java index 7aa4c9821..63eb3bf82 100644 --- a/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java +++ b/core/src/main/java/io/opentelemetry/android/OpenTelemetryRumBuilder.java @@ -38,6 +38,8 @@ import io.opentelemetry.android.internal.services.periodicwork.PeriodicWork; import io.opentelemetry.android.internal.session.SessionIdTimeoutHandler; import io.opentelemetry.android.internal.session.SessionManagerImpl; +import io.opentelemetry.android.internal.session.SessionObservingSessionProvider; +import io.opentelemetry.android.sampling.SessionIdRatioBasedSampler; import io.opentelemetry.android.session.SessionManager; import io.opentelemetry.android.session.SessionProvider; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; @@ -71,6 +73,7 @@ import io.opentelemetry.sdk.trace.SpanProcessor; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -111,6 +114,7 @@ public final class OpenTelemetryRumBuilder { @Nullable private ExportScheduleHandler exportScheduleHandler; @Nullable private SessionManager sessionManager; + private double sessionIdSamplingRatio = Double.NEGATIVE_INFINITY; private static TextMapPropagator buildDefaultPropagator() { return TextMapPropagator.composite( @@ -301,6 +305,24 @@ public OpenTelemetryRumBuilder addLogRecordExporterCustomizer( return this; } + /** + * Enables a ratio-based probabilistic sampling of sessions. When a session is sampled + * (included), then the telemetry for that session will be created and exported. If a session is + * not sampled (excluded) then the telemetry for that session will not be exported. + * + * @param ratio - A number between 0 and 1, representing a percentage of sessions to sample. + */ + public OpenTelemetryRumBuilder enableSessionBasedSampling(double ratio) { + if (ratio < 0.0 || ratio > 1.0) { + Log.e( + RumConstants.OTEL_RUM_LOG_TAG, + "Sampling ratio must be in range [0.0, 1.0], ignoring " + ratio); + return this; + } + sessionIdSamplingRatio = ratio; + return this; + } + /** * Creates a new instance of {@link OpenTelemetryRum} with the settings of this {@link * OpenTelemetryRumBuilder}. @@ -325,6 +347,8 @@ public OpenTelemetryRum build() { sessionManager = SessionManagerImpl.create(timeoutHandler, config.getSessionConfig()); } + configureSessionSampling(); + OpenTelemetrySdk sdk = OpenTelemetrySdk.builder() .setTracerProvider( @@ -360,6 +384,30 @@ public OpenTelemetryRum build() { return delegate.build(); } + private void configureSessionSampling() { + if ((sessionIdSamplingRatio < 0) || (sessionManager == null)) { + return; + } + SessionObservingSessionProvider observerAndProvider = new SessionObservingSessionProvider(); + sessionManager.addObserver(observerAndProvider); + + Sampler sampler = buildSampler(sessionIdSamplingRatio, observerAndProvider); + addTracerProviderCustomizer((builder, application) -> builder.setSampler(sampler)); + + // TODO: When there is a mechanism for sampling logs, we need to wire + // it up here... + } + + private static Sampler buildSampler(double ratio, SessionProvider sessionProvider) { + if (ratio == 0.0) { + return Sampler.alwaysOff(); + } + if (ratio == 1.0) { + return Sampler.alwaysOn(); + } + return new SessionIdRatioBasedSampler(ratio, sessionProvider); + } + private void initializeExporters( Services services, InitializationEvents initializationEvents, diff --git a/core/src/main/java/io/opentelemetry/android/internal/session/SessionObservingSessionProvider.kt b/core/src/main/java/io/opentelemetry/android/internal/session/SessionObservingSessionProvider.kt new file mode 100644 index 000000000..96708b931 --- /dev/null +++ b/core/src/main/java/io/opentelemetry/android/internal/session/SessionObservingSessionProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.android.internal.session + +import io.opentelemetry.android.session.Session +import io.opentelemetry.android.session.SessionObserver +import io.opentelemetry.android.session.SessionProvider +import java.util.concurrent.atomic.AtomicReference + +/** + * A SessionObserver that listens for session start events, stores the session, + * and is also a SessionProvider, that can return the session id. + */ +internal class SessionObservingSessionProvider : + SessionProvider, + SessionObserver { + private val session = AtomicReference(Session.NONE) + + override fun getSessionId(): String = session.get().getId() + + override fun onSessionStarted( + newSession: Session, + previousSession: Session, + ) = session.set(newSession) +} diff --git a/core/src/main/java/io/opentelemetry/android/SessionIdRatioBasedSampler.java b/core/src/main/java/io/opentelemetry/android/sampling/SessionIdRatioBasedSampler.java similarity index 97% rename from core/src/main/java/io/opentelemetry/android/SessionIdRatioBasedSampler.java rename to core/src/main/java/io/opentelemetry/android/sampling/SessionIdRatioBasedSampler.java index fe24469d2..6bc81121c 100644 --- a/core/src/main/java/io/opentelemetry/android/SessionIdRatioBasedSampler.java +++ b/core/src/main/java/io/opentelemetry/android/sampling/SessionIdRatioBasedSampler.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.android; +package io.opentelemetry.android.sampling; import io.opentelemetry.android.session.SessionProvider; import io.opentelemetry.api.common.Attributes; diff --git a/core/src/test/java/io/opentelemetry/android/SessionIdRatioBasedSamplerTest.java b/core/src/test/java/io/opentelemetry/android/SessionIdRatioBasedSamplerTest.java index bb774724d..4712905c5 100644 --- a/core/src/test/java/io/opentelemetry/android/SessionIdRatioBasedSamplerTest.java +++ b/core/src/test/java/io/opentelemetry/android/SessionIdRatioBasedSamplerTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; +import io.opentelemetry.android.sampling.SessionIdRatioBasedSampler; import io.opentelemetry.android.session.SessionProvider; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; diff --git a/session/src/main/kotlin/io/opentelemetry/android/session/SessionObserver.kt b/session/src/main/kotlin/io/opentelemetry/android/session/SessionObserver.kt index 35cccfe4b..6b19bf369 100644 --- a/session/src/main/kotlin/io/opentelemetry/android/session/SessionObserver.kt +++ b/session/src/main/kotlin/io/opentelemetry/android/session/SessionObserver.kt @@ -9,7 +9,7 @@ interface SessionObserver { fun onSessionStarted( newSession: Session, previousSession: Session, - ) + ) {} - fun onSessionEnded(session: Session) + fun onSessionEnded(session: Session) {} }