From 9455f8aabb3fc95d81af5b945b5c41a364248676 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 29 Dec 2025 11:24:41 -0600 Subject: [PATCH 1/3] Add declarative support for composable parent threshold sampler --- .../fileconfig/ComposableSamplerFactory.java | 8 ++ .../fileconfig/SamplerFactoryTest.java | 86 +++++++++++++------ 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ComposableSamplerFactory.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ComposableSamplerFactory.java index 4892e66b1c4..2160d2dcf06 100644 --- a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ComposableSamplerFactory.java +++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ComposableSamplerFactory.java @@ -5,6 +5,7 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableParentThresholdSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableProbabilitySamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableRuleBasedSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableSamplerModel; @@ -43,6 +44,13 @@ public ComposableSampler create( if (ruleBased != null) { return ComposableRuleBasedSamplerFactory.getInstance().create(ruleBased, context); } + ExperimentalComposableParentThresholdSamplerModel parentThreshold = model.getParentThreshold(); + if (parentThreshold != null) { + ExperimentalComposableSamplerModel rootModel = + FileConfigUtil.requireNonNull(parentThreshold.getRoot(), "parent threshold sampler root"); + ComposableSampler rootSampler = INSTANCE.create(rootModel, context); + return ComposableSampler.parentThreshold(rootSampler); + } Map.Entry keyValue = FileConfigUtil.getSingletonMapEntry(model.getAdditionalProperties(), "composable sampler"); return context.loadComponent(ComposableSampler.class, keyValue.getKey(), keyValue.getValue()); diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java index 8fb0ffda39a..abdcf379bc4 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java @@ -15,12 +15,21 @@ import io.opentelemetry.sdk.extension.incubator.fileconfig.component.SamplerComponentProvider; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOffSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.AlwaysOnSamplerModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableAlwaysOffSamplerModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableAlwaysOnSamplerModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableParentThresholdSamplerModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableProbabilitySamplerModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableRuleBasedSamplerModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalJaegerRemoteSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ParentBasedSamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SamplerModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SamplerPropertyModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TraceIdRatioBasedSamplerModel; +import io.opentelemetry.sdk.extension.incubator.trace.samplers.ComposableSampler; +import io.opentelemetry.sdk.extension.incubator.trace.samplers.CompositeSampler; import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler; +import io.opentelemetry.sdk.trace.samplers.Sampler; import java.io.Closeable; import java.time.Duration; import java.util.ArrayList; @@ -45,16 +54,14 @@ class SamplerFactoryTest { @ParameterizedTest @MethodSource("createArguments") - void create( - @Nullable SamplerModel model, io.opentelemetry.sdk.trace.samplers.Sampler expectedSampler) { + void create(@Nullable SamplerModel model, Sampler expectedSampler) { // Some samplers like JaegerRemoteSampler are Closeable - ensure these get cleaned up if (expectedSampler instanceof Closeable) { cleanup.addCloseable((Closeable) expectedSampler); } List closeables = new ArrayList<>(); - io.opentelemetry.sdk.trace.samplers.Sampler sampler = - SamplerFactory.getInstance().create(model, context); + Sampler sampler = SamplerFactory.getInstance().create(model, context); cleanup.addCloseables(closeables); assertThat(sampler.toString()).isEqualTo(expectedSampler.toString()); @@ -63,22 +70,19 @@ void create( private static Stream createArguments() { return Stream.of( Arguments.of( - new SamplerModel().withAlwaysOn(new AlwaysOnSamplerModel()), - io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn()), + new SamplerModel().withAlwaysOn(new AlwaysOnSamplerModel()), Sampler.alwaysOn()), Arguments.of( - new SamplerModel().withAlwaysOff(new AlwaysOffSamplerModel()), - io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOff()), + new SamplerModel().withAlwaysOff(new AlwaysOffSamplerModel()), Sampler.alwaysOff()), Arguments.of( new SamplerModel().withTraceIdRatioBased(new TraceIdRatioBasedSamplerModel()), - io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(1.0d)), + Sampler.traceIdRatioBased(1.0d)), Arguments.of( new SamplerModel() .withTraceIdRatioBased(new TraceIdRatioBasedSamplerModel().withRatio(0.5d)), - io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.5)), + Sampler.traceIdRatioBased(0.5)), Arguments.of( new SamplerModel().withParentBased(new ParentBasedSamplerModel()), - io.opentelemetry.sdk.trace.samplers.Sampler.parentBased( - io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOn())), + Sampler.parentBased(Sampler.alwaysOn())), Arguments.of( new SamplerModel() .withParentBased( @@ -103,16 +107,11 @@ private static Stream createArguments() { new SamplerModel() .withTraceIdRatioBased( new TraceIdRatioBasedSamplerModel().withRatio(0.5d)))), - io.opentelemetry.sdk.trace.samplers.Sampler.parentBasedBuilder( - io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.1d)) - .setRemoteParentSampled( - io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.2d)) - .setRemoteParentNotSampled( - io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.3d)) - .setLocalParentSampled( - io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.4d)) - .setLocalParentNotSampled( - io.opentelemetry.sdk.trace.samplers.Sampler.traceIdRatioBased(0.5d)) + Sampler.parentBasedBuilder(Sampler.traceIdRatioBased(0.1d)) + .setRemoteParentSampled(Sampler.traceIdRatioBased(0.2d)) + .setRemoteParentNotSampled(Sampler.traceIdRatioBased(0.3d)) + .setLocalParentSampled(Sampler.traceIdRatioBased(0.4d)) + .setLocalParentNotSampled(Sampler.traceIdRatioBased(0.5d)) .build()), Arguments.of( new SamplerModel() @@ -125,8 +124,45 @@ private static Stream createArguments() { JaegerRemoteSampler.builder() .setEndpoint("http://jaeger-remote-endpoint") .setPollingInterval(Duration.ofSeconds(10)) - .setInitialSampler(io.opentelemetry.sdk.trace.samplers.Sampler.alwaysOff()) - .build())); + .setInitialSampler(Sampler.alwaysOff()) + .build()), + Arguments.of( + new SamplerModel() + .withCompositeDevelopment( + new ExperimentalComposableSamplerModel() + .withAlwaysOn(new ExperimentalComposableAlwaysOnSamplerModel())), + CompositeSampler.wrap(ComposableSampler.alwaysOn())), + Arguments.of( + new SamplerModel() + .withCompositeDevelopment( + new ExperimentalComposableSamplerModel() + .withAlwaysOff(new ExperimentalComposableAlwaysOffSamplerModel())), + CompositeSampler.wrap(ComposableSampler.alwaysOff())), + Arguments.of( + new SamplerModel() + .withCompositeDevelopment( + new ExperimentalComposableSamplerModel() + .withProbability( + new ExperimentalComposableProbabilitySamplerModel().withRatio(0.5))), + CompositeSampler.wrap(ComposableSampler.probability(0.5))), + Arguments.of( + new SamplerModel() + .withCompositeDevelopment( + new ExperimentalComposableSamplerModel() + .withRuleBased(new ExperimentalComposableRuleBasedSamplerModel())), + CompositeSampler.wrap(ComposableSampler.ruleBasedBuilder().build())), + Arguments.of( + new SamplerModel() + .withCompositeDevelopment( + new ExperimentalComposableSamplerModel() + .withParentThreshold( + new ExperimentalComposableParentThresholdSamplerModel() + .withRoot( + new ExperimentalComposableSamplerModel() + .withAlwaysOn( + new ExperimentalComposableAlwaysOnSamplerModel())))), + CompositeSampler.wrap( + ComposableSampler.parentThreshold(ComposableSampler.alwaysOn())))); } @Test @@ -151,7 +187,7 @@ void create_SpiExporter_Unknown() { @Test void create_SpiExporter_Valid() { - io.opentelemetry.sdk.trace.samplers.Sampler sampler = + Sampler sampler = SamplerFactory.getInstance() .create( new SamplerModel() From e6f162f32f3836574fc53f1948e684562507219b Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 29 Dec 2025 11:56:26 -0600 Subject: [PATCH 2/3] Add failure test --- sdk-extensions/incubator/build.gradle.kts | 1 + .../fileconfig/SamplerFactoryTest.java | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/sdk-extensions/incubator/build.gradle.kts b/sdk-extensions/incubator/build.gradle.kts index 94761a964b5..cc1bd3e6d07 100644 --- a/sdk-extensions/incubator/build.gradle.kts +++ b/sdk-extensions/incubator/build.gradle.kts @@ -199,6 +199,7 @@ tasks.getByName("compileJava").dependsOn(deleteJs2pTmp) tasks.getByName("sourcesJar").dependsOn(deleteJs2pTmp, buildGraalVmReflectionJson) tasks.getByName("jar").dependsOn(deleteJs2pTmp, buildGraalVmReflectionJson) tasks.getByName("javadoc").dependsOn(buildGraalVmReflectionJson) +tasks.getByName("compileTestJava").dependsOn(buildGraalVmReflectionJson) // Exclude jsonschema2pojo generated sources from checkstyle tasks.named("checkstyleMain") { diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java index abdcf379bc4..30bc51d2bf7 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java @@ -165,6 +165,27 @@ private static Stream createArguments() { ComposableSampler.parentThreshold(ComposableSampler.alwaysOn())))); } + @ParameterizedTest + @MethodSource("createInvalidArguments") + void createInvalid(SamplerModel model, String expectedMessage) { + assertThatThrownBy(() -> SamplerFactory.getInstance().create(model, context)) + .isInstanceOf(DeclarativeConfigException.class) + .extracting(throwable -> throwable.getCause() == null ? throwable : throwable.getCause()) + .extracting(Throwable::getMessage) + .isEqualTo(expectedMessage); + } + + private static Stream createInvalidArguments() { + return Stream.of( + Arguments.of( + new SamplerModel() + .withCompositeDevelopment( + new ExperimentalComposableSamplerModel() + .withParentThreshold( + new ExperimentalComposableParentThresholdSamplerModel())), + "parent threshold sampler root is required but is null")); + } + @Test void create_SpiExporter_Unknown() { List closeables = new ArrayList<>(); From 01686cb57b6131eceef9b172dc2e89e35f3da72f Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 29 Dec 2025 12:40:33 -0600 Subject: [PATCH 3/3] merge conflict --- .../fileconfig/SamplerFactoryTest.java | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java index a379df49341..2d6cba3cffb 100644 --- a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java +++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/SamplerFactoryTest.java @@ -175,26 +175,6 @@ void createInvalid(SamplerModel model, String expectedMessage) { .isEqualTo(expectedMessage); } - private static Stream createInvalidArguments() { - return Stream.of( - Arguments.of( - new SamplerModel() - .withCompositeDevelopment( - new ExperimentalComposableSamplerModel() - .withParentThreshold( - new ExperimentalComposableParentThresholdSamplerModel())), - "parent threshold sampler root is required but is null")); - } - - @ParameterizedTest - @MethodSource("createInvalidArguments") - void createInvalid(SamplerModel model, String expectedMessage) { - assertThatThrownBy(() -> SamplerFactory.getInstance().create(model, context)) - .isInstanceOf(DeclarativeConfigException.class) - .cause() - .hasMessage(expectedMessage); - } - private static Stream createInvalidArguments() { return Stream.of( Arguments.of( @@ -206,7 +186,14 @@ private static Stream createInvalidArguments() { .withJaegerRemoteDevelopment( new ExperimentalJaegerRemoteSamplerModel() .withEndpoint("http://jaeger-remote-endpoint")), - "jaeger remote sampler initial_sampler is required")); + "jaeger remote sampler initial_sampler is required"), + Arguments.of( + new SamplerModel() + .withCompositeDevelopment( + new ExperimentalComposableSamplerModel() + .withParentThreshold( + new ExperimentalComposableParentThresholdSamplerModel())), + "parent threshold sampler root is required but is null")); } @Test