Skip to content

Feat/traces sampler into sample rate #2141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.nhaarman.mockitokotlin2.whenever
import io.sentry.Hint
import io.sentry.IHub
import io.sentry.SentryTracer
import io.sentry.TracesSamplingDecision
import io.sentry.TransactionContext
import io.sentry.android.core.ActivityLifecycleIntegration.UI_LOAD_OP
import io.sentry.protocol.MeasurementValue
Expand All @@ -21,7 +22,7 @@ class PerformanceAndroidEventProcessorTest {
val options = SentryAndroidOptions()

val hub = mock<IHub>()
val context = TransactionContext("name", "op", true)
val context = TransactionContext("name", "op", TracesSamplingDecision(true))
val tracer = SentryTracer(context, hub)
val activityFramesTracker = mock<ActivityFramesTracker>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.sentry.SentryTraceHeader
import io.sentry.SentryTracer
import io.sentry.SpanStatus
import io.sentry.TraceContext
import io.sentry.TracesSamplingDecision
import io.sentry.TransactionContext
import io.sentry.protocol.SentryTransaction
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -200,7 +201,7 @@ class SentryApolloInterceptorTest {
private fun executeQuery(sut: ApolloClient = fixture.getSut(), isSpanActive: Boolean = true) = runBlocking {
var tx: ITransaction? = null
if (isSpanActive) {
tx = SentryTracer(TransactionContext("op", "desc", true), fixture.hub)
tx = SentryTracer(TransactionContext("op", "desc", TracesSamplingDecision(true)), fixture.hub)
whenever(fixture.hub.span).thenReturn(tx)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.sentry.SentryOptions
import io.sentry.SentryTraceHeader
import io.sentry.SentryTracer
import io.sentry.SpanStatus
import io.sentry.TracesSamplingDecision
import io.sentry.TransactionContext
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
Expand All @@ -28,7 +29,7 @@ class SentrySpanRestTemplateCustomizerTest {
val hub = mock<IHub>()
val restTemplate = RestTemplateBuilder().build()
var mockServer = MockWebServer()
val transaction = SentryTracer(TransactionContext("aTransaction", "op", true), hub)
val transaction = SentryTracer(TransactionContext("aTransaction", "op", TracesSamplingDecision(true)), hub)
internal val customizer = SentrySpanRestTemplateCustomizer(hub)
val url = mockServer.url("/test/123").toString()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.sentry.SentryOptions
import io.sentry.SentryTraceHeader
import io.sentry.SentryTracer
import io.sentry.SpanStatus
import io.sentry.TracesSamplingDecision
import io.sentry.TransactionContext
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
Expand All @@ -34,7 +35,7 @@ class SentrySpanWebClientCustomizerTest {
lateinit var sentryOptions: SentryOptions
val hub = mock<IHub>()
var mockServer = MockWebServer()
val transaction = SentryTracer(TransactionContext("aTransaction", "op", true), hub)
val transaction = SentryTracer(TransactionContext("aTransaction", "op", TracesSamplingDecision(true)), hub)
private val customizer = SentrySpanWebClientCustomizer(hub)

fun getSut(isTransactionActive: Boolean, status: HttpStatus = HttpStatus.OK, throwIOException: Boolean = false, includeMockServerInTracingOrigins: Boolean = true): WebClient {
Expand Down
32 changes: 28 additions & 4 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ public abstract interface class io/sentry/ITransaction : io/sentry/ISpan {
public abstract fun getEventId ()Lio/sentry/protocol/SentryId;
public abstract fun getLatestActiveSpan ()Lio/sentry/Span;
public abstract fun getName ()Ljava/lang/String;
public abstract fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public abstract fun getSpans ()Ljava/util/List;
public abstract fun isSampled ()Ljava/lang/Boolean;
public abstract fun scheduleFinish ()V
Expand Down Expand Up @@ -661,6 +662,7 @@ public final class io/sentry/NoOpTransaction : io/sentry/ITransaction {
public fun getLatestActiveSpan ()Lio/sentry/Span;
public fun getName ()Ljava/lang/String;
public fun getOperation ()Ljava/lang/String;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getSpanContext ()Lio/sentry/SpanContext;
public fun getSpans ()Ljava/util/List;
public fun getStatus ()Lio/sentry/SpanStatus;
Expand Down Expand Up @@ -1387,6 +1389,7 @@ public final class io/sentry/SentryTracer : io/sentry/ITransaction {
public fun getLatestActiveSpan ()Lio/sentry/Span;
public fun getName ()Ljava/lang/String;
public fun getOperation ()Ljava/lang/String;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getSpanContext ()Lio/sentry/SpanContext;
public fun getSpans ()Ljava/util/List;
public fun getStartTimestamp ()Ljava/util/Date;
Expand Down Expand Up @@ -1489,6 +1492,7 @@ public final class io/sentry/Span : io/sentry/ISpan {
public fun getHighPrecisionTimestamp ()Ljava/lang/Double;
public fun getOperation ()Ljava/lang/String;
public fun getParentSpanId ()Lio/sentry/SpanId;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getSpanContext ()Lio/sentry/SpanContext;
public fun getSpanId ()Lio/sentry/SpanId;
public fun getStartTimestamp ()Ljava/util/Date;
Expand Down Expand Up @@ -1521,14 +1525,15 @@ public class io/sentry/SpanContext : io/sentry/JsonSerializable, io/sentry/JsonU
protected field status Lio/sentry/SpanStatus;
protected field tags Ljava/util/Map;
public fun <init> (Lio/sentry/SpanContext;)V
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding breaking changes to constructors here:
Duplicating the constructors would still break when explicitly passing null as the compiler then doesn't know which version to call. So a cast would be needed to fix. That's why we decided to just modify the constructors and risk breaking stuff this way as they are niche constructors which we expect to not be used by many and it's a rather easy fix to pass a new TracesSamplingDecision(true/false) instead of the boolean.

public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Lio/sentry/SpanStatus;)V
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Ljava/lang/String;Lio/sentry/SpanId;Ljava/lang/Boolean;)V
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Ljava/lang/String;Ljava/lang/String;Lio/sentry/TracesSamplingDecision;Lio/sentry/SpanStatus;)V
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Ljava/lang/String;Lio/sentry/SpanId;Lio/sentry/TracesSamplingDecision;)V
public fun <init> (Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Ljava/lang/Boolean;)V
public fun <init> (Ljava/lang/String;Lio/sentry/TracesSamplingDecision;)V
public fun getDescription ()Ljava/lang/String;
public fun getOperation ()Ljava/lang/String;
public fun getParentSpanId ()Lio/sentry/SpanId;
public fun getSampled ()Ljava/lang/Boolean;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getSpanId ()Lio/sentry/SpanId;
public fun getStatus ()Lio/sentry/SpanStatus;
public fun getTags ()Ljava/util/Map;
Expand All @@ -1538,6 +1543,7 @@ public class io/sentry/SpanContext : io/sentry/JsonSerializable, io/sentry/JsonU
public fun setDescription (Ljava/lang/String;)V
public fun setOperation (Ljava/lang/String;)V
public fun setSampled (Ljava/lang/Boolean;)V
public fun setSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
public fun setStatus (Lio/sentry/SpanStatus;)V
public fun setTag (Ljava/lang/String;Ljava/lang/String;)V
public fun setUnknown (Ljava/util/Map;)V
Expand Down Expand Up @@ -1644,11 +1650,19 @@ public final class io/sentry/TraceContext$JsonKeys {
public static final field SAMPLE_RATE Ljava/lang/String;
public static final field TRACE_ID Ljava/lang/String;
public static final field TRANSACTION Ljava/lang/String;
public static final field USER Ljava/lang/String;
public static final field USER_ID Ljava/lang/String;
public static final field USER_SEGMENT Ljava/lang/String;
public fun <init> ()V
}

public final class io/sentry/TracesSamplingDecision {
public fun <init> (Ljava/lang/Boolean;)V
public fun <init> (Ljava/lang/Boolean;Ljava/lang/Double;)V
public fun getSampleRate ()Ljava/lang/Double;
public fun getSampled ()Ljava/lang/Boolean;
}

public final class io/sentry/TracingOrigins {
public fun <init> ()V
public static fun contain (Ljava/util/List;Ljava/lang/String;)Z
Expand All @@ -1657,10 +1671,11 @@ public final class io/sentry/TracingOrigins {

public final class io/sentry/TransactionContext : io/sentry/SpanContext {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lio/sentry/TracesSamplingDecision;)V
public static fun fromSentryTrace (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SentryTraceHeader;)Lio/sentry/TransactionContext;
public fun getName ()Ljava/lang/String;
public fun getParentSampled ()Ljava/lang/Boolean;
public fun getParentSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun setParentSampled (Ljava/lang/Boolean;)V
}

Expand Down Expand Up @@ -2809,6 +2824,7 @@ public final class io/sentry/protocol/SentryTransaction : io/sentry/SentryBaseEv
public fun <init> (Lio/sentry/SentryTracer;)V
public fun <init> (Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;Ljava/util/List;Ljava/util/Map;)V
public fun getMeasurements ()Ljava/util/Map;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getSpans ()Ljava/util/List;
public fun getStartTimestamp ()Ljava/lang/Double;
public fun getStatus ()Lio/sentry/SpanStatus;
Expand Down Expand Up @@ -3018,6 +3034,14 @@ public final class io/sentry/util/Platform {
public static fun isJvm ()Z
}

public final class io/sentry/util/SampleRateUtil {
public fun <init> ()V
public static fun isValidSampleRate (Ljava/lang/Double;)Z
public static fun isValidSampleRate (Ljava/lang/Double;Z)Z
public static fun isValidTracesSampleRate (Ljava/lang/Double;)Z
public static fun isValidTracesSampleRate (Ljava/lang/Double;Z)Z
}

public final class io/sentry/util/StringUtils {
public static fun byteCountToString (J)Ljava/lang/String;
public static fun calculateStringHash (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/String;
Expand Down
6 changes: 3 additions & 3 deletions sentry/src/main/java/io/sentry/Hub.java
Original file line number Diff line number Diff line change
Expand Up @@ -730,8 +730,8 @@ public void flush(long timeoutMillis) {
} else {
final SamplingContext samplingContext =
new SamplingContext(transactionContext, customSamplingContext);
boolean samplingDecision = tracesSampler.sample(samplingContext);
transactionContext.setSampled(samplingDecision);
@NotNull TracesSamplingDecision samplingDecision = tracesSampler.sample(samplingContext);
transactionContext.setSamplingDecision(samplingDecision);

transaction =
new SentryTracer(
Expand All @@ -745,7 +745,7 @@ public void flush(long timeoutMillis) {

// The listener is called only if the transaction exists, as the transaction is needed to
// stop it
if (samplingDecision && options.isProfilingEnabled()) {
if (samplingDecision.getSampled() && options.isProfilingEnabled()) {
final ITransactionProfiler transactionProfiler = options.getTransactionProfiler();
transactionProfiler.onTransactionStart(transaction);
}
Expand Down
3 changes: 3 additions & 0 deletions sentry/src/main/java/io/sentry/ITransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public interface ITransaction extends ISpan {
@Nullable
Boolean isSampled();

@Nullable
TracesSamplingDecision getSamplingDecision();

/**
* Returns the latest span that is not finished.
*
Expand Down
5 changes: 5 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ public void setTag(@NotNull String key, @NotNull String value) {}
return null;
}

@Override
public @Nullable TracesSamplingDecision getSamplingDecision() {
return null;
}

@Override
public void setData(@NotNull String key, @NotNull Object value) {}

Expand Down
41 changes: 37 additions & 4 deletions sentry/src/main/java/io/sentry/OutboxSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.sentry.util.HintUtils;
import io.sentry.util.LogUtils;
import io.sentry.util.Objects;
import io.sentry.util.SampleRateUtil;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
Expand Down Expand Up @@ -161,12 +162,17 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN
continue;
}

// if there is no trace context header we also won't send it to Sentry
final @Nullable TraceContext traceContext = envelope.getHeader().getTraceContext();
if (transaction.getContexts().getTrace() != null) {
// Hint: Set sampled in order for the transaction not to be dropped, as this is a
// transient property.
transaction.getContexts().getTrace().setSampled(true);
// Hint: Set sampling decision in order for the transaction not to be dropped, as this
// is a transient property.
transaction
.getContexts()
.getTrace()
.setSamplingDecision(extractSamplingDecision(traceContext));
}
hub.captureTransaction(transaction, envelope.getHeader().getTraceContext(), hint);
hub.captureTransaction(transaction, traceContext, hint);
logItemCaptured(currentItem);

if (!waitFlush(hint)) {
Expand Down Expand Up @@ -216,6 +222,33 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN
}
}

private @NotNull TracesSamplingDecision extractSamplingDecision(
final @Nullable TraceContext traceContext) {
if (traceContext != null) {
final @Nullable String sampleRateString = traceContext.getSampleRate();
if (sampleRateString != null) {
try {
final Double sampleRate = Double.parseDouble(sampleRateString);
if (!SampleRateUtil.isValidTracesSampleRate(sampleRate, false)) {
logger.log(
SentryLevel.ERROR,
"Invalid sample rate parsed from TraceContext: %s",
sampleRateString);
} else {
return new TracesSamplingDecision(true, sampleRate);
}
} catch (Exception e) {
logger.log(
SentryLevel.ERROR,
"Unable to parse sample rate from TraceContext: %s",
sampleRateString);
}
}
}

return new TracesSamplingDecision(true);
}

private void logEnvelopeItemNull(final @NotNull SentryEnvelopeItem item, int itemIndex) {
logger.log(
SentryLevel.ERROR,
Expand Down
5 changes: 3 additions & 2 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.sentry.transport.NoOpEnvelopeCache;
import io.sentry.transport.NoOpTransportGate;
import io.sentry.util.Platform;
import io.sentry.util.SampleRateUtil;
import io.sentry.util.StringUtils;
import java.io.File;
import java.util.ArrayList;
Expand Down Expand Up @@ -729,7 +730,7 @@ public void setProxy(@Nullable Proxy proxy) {
* @param sampleRate the sample rate
*/
public void setSampleRate(Double sampleRate) {
if (sampleRate != null && (sampleRate > 1.0 || sampleRate <= 0.0)) {
if (!SampleRateUtil.isValidSampleRate(sampleRate)) {
throw new IllegalArgumentException(
"The value "
+ sampleRate
Expand All @@ -753,7 +754,7 @@ public void setSampleRate(Double sampleRate) {
* @param tracesSampleRate the sample rate
*/
public void setTracesSampleRate(final @Nullable Double tracesSampleRate) {
if (tracesSampleRate != null && (tracesSampleRate > 1.0 || tracesSampleRate < 0.0)) {
if (!SampleRateUtil.isValidTracesSampleRate(tracesSampleRate)) {
throw new IllegalArgumentException(
"The value "
+ tracesSampleRate
Expand Down
9 changes: 8 additions & 1 deletion sentry/src/main/java/io/sentry/SentryTracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,9 @@ public void finish(@Nullable SpanStatus status) {
scope -> {
userAtomicReference.set(scope.getUser());
});
this.traceContext = new TraceContext(this, userAtomicReference.get(), hub.getOptions());
this.traceContext =
new TraceContext(
this, userAtomicReference.get(), hub.getOptions(), this.getSamplingDecision());
}
return this.traceContext;
}
Expand Down Expand Up @@ -502,6 +504,11 @@ public void setData(@NotNull String key, @NotNull Object value) {
return this.root.isSampled();
}

@Override
public @Nullable TracesSamplingDecision getSamplingDecision() {
return this.root.getSamplingDecision();
}

@Override
public void setName(@NotNull String name) {
if (root.isFinished()) {
Expand Down
7 changes: 6 additions & 1 deletion sentry/src/main/java/io/sentry/Span.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public final class Span implements ISpan {
final @Nullable Date startTimestamp,
final @Nullable SpanFinishedCallback spanFinishedCallback) {
this.context =
new SpanContext(traceId, new SpanId(), operation, parentSpanId, transaction.isSampled());
new SpanContext(
traceId, new SpanId(), operation, parentSpanId, transaction.getSamplingDecision());
this.transaction = Objects.requireNonNull(transaction, "transaction is required");
this.hub = Objects.requireNonNull(hub, "hub is required");
this.spanFinishedCallback = spanFinishedCallback;
Expand Down Expand Up @@ -258,6 +259,10 @@ public boolean isFinished() {
return context.getSampled();
}

public @Nullable TracesSamplingDecision getSamplingDecision() {
return context.getSamplingDecision();
}

@Override
public void setThrowable(final @Nullable Throwable throwable) {
if (finished.get()) {
Expand Down
Loading