diff --git a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/Samplers.java b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/Samplers.java index 1ee41173e0f..80f1c9b90ea 100644 --- a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/Samplers.java +++ b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/Samplers.java @@ -147,14 +147,27 @@ public static Sampler alwaysOff() { * whether or not to sample. If there is no parent, the Sampler uses the provided Sampler delegate * to determine the sampling decision. * - * @param delegateSampler the {@code Sampler} which is used to make the sampling decisions if the - * parent does not exist. + * @param root the {@code Sampler} which is used to make the sampling decisions if the parent does + * not exist. * @return a {@code Sampler} that follows the parent's sampling decision if one exists, otherwise - * following the delegate sampler's decision. + * following the root sampler's decision. * @since 0.7.0 */ - public static Sampler parentOrElse(Sampler delegateSampler) { - return new ParentOrElse(delegateSampler); + public static Sampler parentBased(Sampler root) { + return parentBasedBuilder(root).build(); + } + + /** + * Returns a {@link ParentBased.Builder} that follows the parent's sampling decision if one + * exists, otherwise following the root sampler and other optional sampler's decision. + * + * @param root the required {@code Sampler} which is used to make the sampling decisions if the + * parent does not exist. + * @return a {@code ParentBased.Builder} + * @since 0.8.0 + */ + public static ParentBased.Builder parentBasedBuilder(Sampler root) { + return new ParentBased.Builder(root); } /** @@ -214,11 +227,26 @@ public String getDescription() { } @Immutable - static class ParentOrElse implements Sampler { - private final Sampler delegateSampler; - - ParentOrElse(Sampler delegateSampler) { - this.delegateSampler = delegateSampler; + static class ParentBased implements Sampler { + private final Sampler root; + private final Sampler remoteParentSampled; + private final Sampler remoteParentNotSampled; + private final Sampler localParentSampled; + private final Sampler localParentNotSampled; + + private ParentBased( + Sampler root, + Sampler remoteParentSampled, + Sampler remoteParentNotSampled, + Sampler localParentSampled, + Sampler localParentNotSampled) { + this.root = root; + this.remoteParentSampled = remoteParentSampled == null ? alwaysOn() : remoteParentSampled; + this.remoteParentNotSampled = + remoteParentNotSampled == null ? alwaysOff() : remoteParentNotSampled; + this.localParentSampled = localParentSampled == null ? alwaysOn() : localParentSampled; + this.localParentNotSampled = + localParentNotSampled == null ? alwaysOff() : localParentNotSampled; } // If a parent is set, always follows the same sampling decision as the parent. @@ -231,19 +259,105 @@ public SamplingResult shouldSample( Kind spanKind, ReadableAttributes attributes, List parentLinks) { - if (parentContext.isValid()) { - if (parentContext.getTraceFlags().isSampled()) { - return EMPTY_RECORDED_AND_SAMPLED_SAMPLING_RESULT; - } - return EMPTY_NOT_SAMPLED_OR_RECORDED_SAMPLING_RESULT; + if (!parentContext.isValid()) { + return this.root.shouldSample( + parentContext, traceId, name, spanKind, attributes, parentLinks); } - return this.delegateSampler.shouldSample( - parentContext, traceId, name, spanKind, attributes, parentLinks); + + if (parentContext.isRemote()) { + return parentContext.getTraceFlags().isSampled() + ? this.remoteParentSampled.shouldSample( + parentContext, traceId, name, spanKind, attributes, parentLinks) + : this.remoteParentNotSampled.shouldSample( + parentContext, traceId, name, spanKind, attributes, parentLinks); + } + return parentContext.getTraceFlags().isSampled() + ? this.localParentSampled.shouldSample( + parentContext, traceId, name, spanKind, attributes, parentLinks) + : this.localParentNotSampled.shouldSample( + parentContext, traceId, name, spanKind, attributes, parentLinks); } @Override public String getDescription() { - return String.format("ParentOrElse{%s}", this.delegateSampler.getDescription()); + return String.format( + "ParentBased{root:%s,remoteParentSampled:%s,remoteParentNotSampled:%s," + + "localParentSampled:%s,localParentNotSampled:%s}", + this.root.getDescription(), + this.remoteParentSampled.getDescription(), + this.remoteParentNotSampled.getDescription(), + this.localParentSampled.getDescription(), + this.localParentNotSampled.getDescription()); + } + + static class Builder { + private final Sampler root; + private Sampler remoteParentSampled; + private Sampler remoteParentNotSampled; + private Sampler localParentSampled; + private Sampler localParentNotSampled; + + /** + * Sets the {@link Sampler} to use when there is a remote parent that was sampled. If not set, + * defaults to always sampling if the remote parent was sampled. + * + * @return this Builder + */ + public Builder setRemoteParentSampled(Sampler remoteParentSampled) { + this.remoteParentSampled = remoteParentSampled; + return this; + } + + /** + * Sets the {@link Sampler} to use when there is a remote parent that was not sampled. If not + * set, defaults to never sampling when the remote parent isn't sampled. + * + * @return this Builder + */ + public Builder setRemoteParentNotSampled(Sampler remoteParentNotSampled) { + this.remoteParentNotSampled = remoteParentNotSampled; + return this; + } + + /** + * Sets the {@link Sampler} to use when there is a local parent that was sampled. If not set, + * defaults to always sampling if the local parent was sampled. + * + * @return this Builder + */ + public Builder setLocalParentSampled(Sampler localParentSampled) { + this.localParentSampled = localParentSampled; + return this; + } + + /** + * Sets the {@link Sampler} to use when there is a local parent that was not sampled. If not + * set, defaults to never sampling when the local parent isn't sampled. + * + * @return this Builder + */ + public Builder setLocalParentNotSampled(Sampler localParentNotSampled) { + this.localParentNotSampled = localParentNotSampled; + return this; + } + + /** + * Builds the {@link ParentBased}. + * + * @return the ParentBased sampler. + */ + public ParentBased build() { + return new ParentBased( + this.root, + this.remoteParentSampled, + this.remoteParentNotSampled, + this.localParentSampled, + this.localParentNotSampled); + } + + private Builder(Sampler root) { + this.root = root; + } } } diff --git a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/config/TraceConfig.java b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/config/TraceConfig.java index 470864582d4..56b6bf86e72 100644 --- a/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/config/TraceConfig.java +++ b/sdk/tracing/src/main/java/io/opentelemetry/sdk/trace/config/TraceConfig.java @@ -87,7 +87,7 @@ public abstract class TraceConfig { // These values are the default values for all the global parameters. // TODO: decide which default sampler to use - private static final Sampler DEFAULT_SAMPLER = Samplers.parentOrElse(Samplers.alwaysOn()); + private static final Sampler DEFAULT_SAMPLER = Samplers.parentBased(Samplers.alwaysOn()); private static final int DEFAULT_SPAN_MAX_NUM_ATTRIBUTES = 32; private static final int DEFAULT_SPAN_MAX_NUM_EVENTS = 128; private static final int DEFAULT_SPAN_MAX_NUM_LINKS = 32; diff --git a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java index 22524c628e9..dee7cfb0d34 100644 --- a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java +++ b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java @@ -51,6 +51,12 @@ class SamplersTest { SpanContext.create(traceId, parentSpanId, TraceFlags.getDefault(), traceState); private final SpanContext invalidSpanContext = SpanContext.getInvalid(); private final io.opentelemetry.trace.Link sampledParentLink = Link.create(sampledSpanContext); + private final SpanContext sampledRemoteSpanContext = + SpanContext.createFromRemoteParent( + traceId, parentSpanId, TraceFlags.builder().setIsSampled(true).build(), traceState); + private final SpanContext notSampledRemoteSpanContext = + SpanContext.createFromRemoteParent( + traceId, parentSpanId, TraceFlags.getDefault(), traceState); @Test void emptySamplingDecision() { @@ -192,10 +198,10 @@ void alwaysOffSampler_GetDescription() { } @Test - void parentOrElseSampler_AlwaysOn() { + void parentBasedSampler_AlwaysOn() { // Sampled parent. assertThat( - Samplers.parentOrElse(Samplers.alwaysOn()) + Samplers.parentBased(Samplers.alwaysOn()) .shouldSample( sampledSpanContext, traceId, @@ -208,7 +214,7 @@ void parentOrElseSampler_AlwaysOn() { // Not sampled parent. assertThat( - Samplers.parentOrElse(Samplers.alwaysOn()) + Samplers.parentBased(Samplers.alwaysOn()) .shouldSample( notSampledSpanContext, traceId, @@ -218,12 +224,15 @@ void parentOrElseSampler_AlwaysOn() { Collections.emptyList()) .getDecision()) .isEqualTo(Decision.NOT_RECORD); + } - // Invalid parent. + @Test + void parentBasedSampler_AlwaysOff() { + // Sampled parent. assertThat( - Samplers.parentOrElse(Samplers.alwaysOn()) + Samplers.parentBased(Samplers.alwaysOff()) .shouldSample( - invalidSpanContext, + sampledSpanContext, traceId, SPAN_NAME, SPAN_KIND, @@ -231,15 +240,89 @@ void parentOrElseSampler_AlwaysOn() { Collections.emptyList()) .getDecision()) .isEqualTo(Decision.RECORD_AND_SAMPLED); + + // Not sampled parent. + assertThat( + Samplers.parentBased(Samplers.alwaysOff()) + .shouldSample( + notSampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); } @Test - void parentOrElseSampler_AlwaysOff() { - // Sampled parent. + void parentBasedSampler_NotSampled_Remote_Parent() { assertThat( - Samplers.parentOrElse(Samplers.alwaysOff()) + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setRemoteParentNotSampled(Samplers.alwaysOn()) + .build() .shouldSample( - sampledSpanContext, + notSampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setRemoteParentNotSampled(Samplers.alwaysOff()) + .build() + .shouldSample( + notSampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setRemoteParentNotSampled(Samplers.alwaysOff()) + .build() + .shouldSample( + notSampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setRemoteParentNotSampled(Samplers.alwaysOn()) + .build() + .shouldSample( + notSampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); + } + + @Test + void parentBasedSampler_NotSampled_NotRemote_Parent() { + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setLocalParentNotSampled(Samplers.alwaysOn()) + .build() + .shouldSample( + notSampledSpanContext, traceId, SPAN_NAME, SPAN_KIND, @@ -248,9 +331,10 @@ void parentOrElseSampler_AlwaysOff() { .getDecision()) .isEqualTo(Decision.RECORD_AND_SAMPLED); - // Not sampled parent. assertThat( - Samplers.parentOrElse(Samplers.alwaysOff()) + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setLocalParentNotSampled(Samplers.alwaysOff()) + .build() .shouldSample( notSampledSpanContext, traceId, @@ -261,9 +345,186 @@ void parentOrElseSampler_AlwaysOff() { .getDecision()) .isEqualTo(Decision.NOT_RECORD); - // Invalid parent. assertThat( - Samplers.parentOrElse(Samplers.alwaysOff()) + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setLocalParentNotSampled(Samplers.alwaysOff()) + .build() + .shouldSample( + notSampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setLocalParentNotSampled(Samplers.alwaysOn()) + .build() + .shouldSample( + notSampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); + } + + @Test + void parentBasedSampler_Sampled_Remote_Parent() { + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setRemoteParentSampled(Samplers.alwaysOff()) + .build() + .shouldSample( + sampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setRemoteParentSampled(Samplers.alwaysOn()) + .build() + .shouldSample( + sampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setRemoteParentSampled(Samplers.alwaysOn()) + .build() + .shouldSample( + sampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setRemoteParentSampled(Samplers.alwaysOff()) + .build() + .shouldSample( + sampledRemoteSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + } + + @Test + void parentBasedSampler_Sampled_NotRemote_Parent() { + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setLocalParentSampled(Samplers.alwaysOn()) + .build() + .shouldSample( + sampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setLocalParentSampled(Samplers.alwaysOff()) + .build() + .shouldSample( + sampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setLocalParentSampled(Samplers.alwaysOff()) + .build() + .shouldSample( + sampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOn()) + .setLocalParentSampled(Samplers.alwaysOn()) + .build() + .shouldSample( + sampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); + } + + @Test + void parentBasedSampler_invalid_Parent() { + assertThat( + Samplers.parentBased(Samplers.alwaysOff()) + .shouldSample( + invalidSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBased(Samplers.alwaysOff()) + .shouldSample( + invalidSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBasedBuilder(Samplers.alwaysOff()) + .setRemoteParentSampled(Samplers.alwaysOn()) + .setRemoteParentNotSampled(Samplers.alwaysOn()) + .setLocalParentSampled(Samplers.alwaysOn()) + .setLocalParentNotSampled(Samplers.alwaysOn()) + .build() .shouldSample( invalidSpanContext, traceId, @@ -273,12 +534,27 @@ void parentOrElseSampler_AlwaysOff() { Collections.emptyList()) .getDecision()) .isEqualTo(Decision.NOT_RECORD); + + assertThat( + Samplers.parentBased(Samplers.alwaysOn()) + .shouldSample( + invalidSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .getDecision()) + .isEqualTo(Decision.RECORD_AND_SAMPLED); } @Test - void parentOrElseSampler_GetDescription() { - assertThat(Samplers.parentOrElse(Samplers.alwaysOn()).getDescription()) - .isEqualTo("ParentOrElse{AlwaysOnSampler}"); + void parentBasedSampler_GetDescription() { + assertThat(Samplers.parentBased(Samplers.alwaysOn()).getDescription()) + .isEqualTo( + "ParentBased{root:AlwaysOnSampler,remoteParentSampled:AlwaysOnSampler," + + "remoteParentNotSampled:AlwaysOffSampler,localParentSampled:AlwaysOnSampler," + + "localParentNotSampled:AlwaysOffSampler}"); } @Test diff --git a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/config/TraceConfigTest.java b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/config/TraceConfigTest.java index 9ad5ec8947d..c7865eb6097 100644 --- a/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/config/TraceConfigTest.java +++ b/sdk/tracing/src/test/java/io/opentelemetry/sdk/trace/config/TraceConfigTest.java @@ -28,7 +28,7 @@ class TraceConfigTest { @Test void defaultTraceConfig() { assertThat(TraceConfig.getDefault().getSampler().getDescription()) - .isEqualTo(Samplers.parentOrElse(Samplers.alwaysOn()).getDescription()); + .isEqualTo(Samplers.parentBased(Samplers.alwaysOn()).getDescription()); assertThat(TraceConfig.getDefault().getMaxNumberOfAttributes()).isEqualTo(32); assertThat(TraceConfig.getDefault().getMaxNumberOfEvents()).isEqualTo(128); assertThat(TraceConfig.getDefault().getMaxNumberOfLinks()).isEqualTo(32);