diff --git a/dd-trace-core/src/main/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSampler.java b/dd-trace-core/src/main/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSampler.java
new file mode 100644
index 00000000000..6e8e42dc2d8
--- /dev/null
+++ b/dd-trace-core/src/main/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSampler.java
@@ -0,0 +1,24 @@
+package datadog.trace.common.sampling;
+
+import datadog.trace.api.sampling.PrioritySampling;
+import datadog.trace.api.sampling.SamplingMechanism;
+import datadog.trace.core.CoreSpan;
+
+/**
+ * Implements the OpenTelemetry {@code parentbased_always_on} sampler.
+ *
+ *
Root spans are always sampled. Child spans inherit the sampling decision from their parent,
+ * which is handled by the context propagation layer.
+ */
+public class ParentBasedAlwaysOnSampler implements Sampler, PrioritySampler {
+
+ @Override
+ public > boolean sample(final T span) {
+ return true;
+ }
+
+ @Override
+ public > void setSamplingPriority(final T span) {
+ span.setSamplingPriority(PrioritySampling.SAMPLER_KEEP, SamplingMechanism.DEFAULT);
+ }
+}
diff --git a/dd-trace-core/src/main/java/datadog/trace/common/sampling/Sampler.java b/dd-trace-core/src/main/java/datadog/trace/common/sampling/Sampler.java
index 7cecdf40ed9..af1045e39df 100644
--- a/dd-trace-core/src/main/java/datadog/trace/common/sampling/Sampler.java
+++ b/dd-trace-core/src/main/java/datadog/trace/common/sampling/Sampler.java
@@ -80,6 +80,7 @@ public static Sampler forConfig(final Config config, final TraceConfig traceConf
log.error("Invalid sampler configuration. Using AllSampler", e);
sampler = new AllSampler();
}
+ // TODO: if OTLP trace export enabled, select ParentBasedAlwaysOnSampler here
} else if (config.isPrioritySamplingEnabled()) {
if (KEEP.equalsIgnoreCase(config.getPrioritySamplingForce())) {
log.debug("Force Sampling Priority to: SAMPLER_KEEP.");
diff --git a/dd-trace-core/src/test/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.java b/dd-trace-core/src/test/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.java
new file mode 100644
index 00000000000..567a5baa9d2
--- /dev/null
+++ b/dd-trace-core/src/test/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.java
@@ -0,0 +1,124 @@
+package datadog.trace.common.sampling;
+
+import static datadog.trace.api.TracePropagationStyle.DATADOG;
+import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP;
+import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP;
+import static datadog.trace.api.sampling.PrioritySampling.USER_DROP;
+import static datadog.trace.api.sampling.PrioritySampling.USER_KEEP;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import datadog.trace.api.DDTraceId;
+import datadog.trace.common.writer.ListWriter;
+import datadog.trace.common.writer.RemoteResponseListener;
+import datadog.trace.core.CoreTracer;
+import datadog.trace.core.DDSpan;
+import datadog.trace.core.propagation.ExtractedContext;
+import datadog.trace.core.propagation.PropagationTags;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class ParentBasedAlwaysOnSamplerTest {
+
+ private final ListWriter writer = new ListWriter();
+ private CoreTracer tracer;
+
+ @AfterEach
+ void tearDown() {
+ if (tracer != null) {
+ tracer.close();
+ }
+ }
+
+ private CoreTracer buildTracer(ParentBasedAlwaysOnSampler sampler) {
+ tracer = CoreTracer.builder().writer(writer).sampler(sampler).build();
+ return tracer;
+ }
+
+ @Test
+ void alwaysSamplesSpans() {
+ ParentBasedAlwaysOnSampler sampler = new ParentBasedAlwaysOnSampler();
+ CoreTracer tracer = buildTracer(sampler);
+
+ DDSpan span = (DDSpan) tracer.buildSpan("test").start();
+ try {
+ assertTrue(sampler.sample(span));
+ } finally {
+ span.finish();
+ }
+ }
+
+ @Test
+ void setsSamplingPriorityToSamplerKeep() {
+ ParentBasedAlwaysOnSampler sampler = new ParentBasedAlwaysOnSampler();
+ CoreTracer tracer = buildTracer(sampler);
+
+ DDSpan span = (DDSpan) tracer.buildSpan("test").start();
+ try {
+ sampler.setSamplingPriority(span);
+ assertEquals(SAMPLER_KEEP, span.getSamplingPriority());
+ } finally {
+ span.finish();
+ }
+ }
+
+ @Test
+ void childSpanInheritsSamplingPriorityFromLocalParent() {
+ ParentBasedAlwaysOnSampler sampler = new ParentBasedAlwaysOnSampler();
+ CoreTracer tracer = buildTracer(sampler);
+
+ DDSpan rootSpan = (DDSpan) tracer.buildSpan("root").start();
+ sampler.setSamplingPriority(rootSpan);
+ DDSpan childSpan = (DDSpan) tracer.buildSpan("child").asChildOf(rootSpan.context()).start();
+ try {
+ assertEquals(SAMPLER_KEEP, rootSpan.getSamplingPriority());
+ assertEquals(SAMPLER_KEEP, childSpan.getSamplingPriority());
+ } finally {
+ childSpan.finish();
+ rootSpan.finish();
+ }
+ }
+
+ static Stream childSpanInheritsSamplingDecisionFromRemoteParentArguments() {
+ return Stream.of(
+ Arguments.arguments("sampler keep", SAMPLER_KEEP),
+ Arguments.arguments("sampler drop", SAMPLER_DROP),
+ Arguments.arguments("user keep", USER_KEEP),
+ Arguments.arguments("user drop", USER_DROP));
+ }
+
+ @ParameterizedTest(name = "child span inherits sampling decision from remote parent: {0}")
+ @MethodSource("childSpanInheritsSamplingDecisionFromRemoteParentArguments")
+ void childSpanInheritsSamplingDecisionFromRemoteParent(String scenario, int parentPriority) {
+ ParentBasedAlwaysOnSampler sampler = new ParentBasedAlwaysOnSampler();
+ CoreTracer tracer = buildTracer(sampler);
+
+ ExtractedContext extractedContext =
+ new ExtractedContext(
+ DDTraceId.ONE, 2, parentPriority, null, PropagationTags.factory().empty(), DATADOG);
+
+ DDSpan span = (DDSpan) tracer.buildSpan("child").asChildOf(extractedContext).start();
+ try {
+ assertEquals(parentPriority, span.getSamplingPriority());
+ } finally {
+ span.finish();
+ }
+ }
+
+ @Test
+ void isNotARemoteResponseListener() {
+ ParentBasedAlwaysOnSampler sampler = new ParentBasedAlwaysOnSampler();
+ assertFalse(sampler instanceof RemoteResponseListener);
+ }
+
+ @Test
+ void implementsPrioritySampler() {
+ ParentBasedAlwaysOnSampler sampler = new ParentBasedAlwaysOnSampler();
+ assertTrue(sampler instanceof PrioritySampler);
+ }
+}