diff --git a/sdk/src/main/java/io/opentelemetry/sdk/trace/Samplers.java b/sdk/src/main/java/io/opentelemetry/sdk/trace/Samplers.java index 348e292064..22ffec6bc1 100644 --- a/sdk/src/main/java/io/opentelemetry/sdk/trace/Samplers.java +++ b/sdk/src/main/java/io/opentelemetry/sdk/trace/Samplers.java @@ -139,6 +139,21 @@ public final class Samplers { return AlwaysOffSampler.INSTANCE; } + /** + * Returns a {@link Sampler} that always makes the same decision as the parent {@link Span} to + * 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. + * @return a {@code Sampler} that follows the parent's sampling decision if one exists, otherwise + * following the delegate sampler's decision. + * @since 0.7.0 + */ + public static Sampler parentOrElse(Sampler delegateSampler) { + return new ParentOrElse(delegateSampler); + } + /** * Returns a new Probability {@link Sampler}. The probability of sampling a trace is equal to that * of the specified probability. @@ -195,6 +210,40 @@ public final class Samplers { } } + @Immutable + static class ParentOrElse implements Sampler { + private final Sampler delegateSampler; + + ParentOrElse(Sampler delegateSampler) { + this.delegateSampler = delegateSampler; + } + + // If a parent is set, always follows the same sampling decision as the parent. + // Otherwise, uses the delegateSampler provided at initialization to make a decision. + @Override + public Decision shouldSample( + @Nullable SpanContext parentContext, + TraceId traceId, + String name, + Kind spanKind, + ReadableAttributes attributes, + List parentLinks) { + if (parentContext != null) { + if (parentContext.getTraceFlags().isSampled()) { + return EMPTY_SAMPLED_DECISION; + } + return EMPTY_NOT_SAMPLED_DECISION; + } + return this.delegateSampler.shouldSample( + parentContext, traceId, name, spanKind, attributes, parentLinks); + } + + @Override + public String getDescription() { + return String.format("ParentOrElseSampler-%s", this.delegateSampler.getDescription()); + } + } + /** * We assume the lower 64 bits of the traceId's are randomly distributed around the whole (long) * range. We convert an incoming probability into an upper bound on that value, such that we can diff --git a/sdk/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java b/sdk/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java index 03c66bc511..b5a8c22c73 100644 --- a/sdk/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java +++ b/sdk/src/test/java/io/opentelemetry/sdk/trace/SamplersTest.java @@ -193,6 +193,96 @@ public class SamplersTest { assertThat(Samplers.alwaysOff().getDescription()).isEqualTo("AlwaysOffSampler"); } + @Test + public void parentOrElseSampler_AlwaysOn() { + // Sampled parent. + assertThat( + Samplers.parentOrElse(Samplers.alwaysOn()) + .shouldSample( + sampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .isSampled()) + .isTrue(); + + // Not sampled parent. + assertThat( + Samplers.parentOrElse(Samplers.alwaysOn()) + .shouldSample( + notSampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .isSampled()) + .isFalse(); + + // Null parent. + assertThat( + Samplers.parentOrElse(Samplers.alwaysOn()) + .shouldSample( + null, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .isSampled()) + .isTrue(); + } + + @Test + public void parentOrElseSampler_AlwaysOff() { + // Sampled parent. + assertThat( + Samplers.parentOrElse(Samplers.alwaysOff()) + .shouldSample( + sampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .isSampled()) + .isTrue(); + + // Not sampled parent. + assertThat( + Samplers.parentOrElse(Samplers.alwaysOff()) + .shouldSample( + notSampledSpanContext, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .isSampled()) + .isFalse(); + + // Null parent. + assertThat( + Samplers.parentOrElse(Samplers.alwaysOff()) + .shouldSample( + null, + traceId, + SPAN_NAME, + SPAN_KIND, + Attributes.empty(), + Collections.emptyList()) + .isSampled()) + .isFalse(); + } + + @Test + public void parentOrElseSampler_GetDescription() { + assertThat(Samplers.parentOrElse(Samplers.alwaysOn()).getDescription()) + .isEqualTo("ParentOrElseSampler-AlwaysOnSampler"); + } + @Test public void probabilitySampler_AlwaysSample() { Samplers.Probability sampler = Samplers.Probability.create(1);