From bb0ec0ac51072d14977ac0a0c152565f1592276d Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 20 Mar 2026 11:36:00 -0400 Subject: [PATCH 1/4] Introduce ParentBasedAlwaysOnSampler and make this the default sampler for otlp mode, instead of priority sampling or traces rate based sampler. plus tests --- .../java/datadog/json/JsonMapperTest.java | 52 ++++---- .../java/datadog/trace/api/DDSpanIdTest.java | 52 ++++---- .../java/datadog/trace/api/DDTraceIdTest.java | 104 ++++++++-------- .../trace/api/IdGenerationStrategyTest.java | 6 +- .../api/internal/util/HexStringUtilsTest.java | 8 +- .../sampling/ParentBasedAlwaysOnSampler.java | 24 ++++ .../trace/common/sampling/Sampler.java | 2 + .../ParentBasedAlwaysOnSamplerTest.groovy | 114 ++++++++++++++++++ .../trace/common/sampling/SamplerTest.groovy | 49 ++++++++ 9 files changed, 300 insertions(+), 111 deletions(-) create mode 100644 dd-trace-core/src/main/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSampler.java create mode 100644 dd-trace-core/src/test/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy diff --git a/components/json/src/test/java/datadog/json/JsonMapperTest.java b/components/json/src/test/java/datadog/json/JsonMapperTest.java index 926702881bc..eef94bd5032 100644 --- a/components/json/src/test/java/datadog/json/JsonMapperTest.java +++ b/components/json/src/test/java/datadog/json/JsonMapperTest.java @@ -24,12 +24,12 @@ class JsonMapperTest { @TableTest({ - "Scenario | Input | Expected ", - "null input | | '{}' ", - "empty map | [:] | '{}' ", - "single entry | [key1: value1] | '{\"key1\":\"value1\"}' ", - "two entries | [key1: value1, key2: value2] | '{\"key1\":\"value1\",\"key2\":\"value2\"}' ", - "quoted entries | [key1: va\"lu\"e1, ke\"y2: value2] | '{\"key1\":\"va\\\"lu\\\"e1\",\"ke\\\"y2\":\"value2\"}'" + "Scenario | Input | Expected ", + "null input | | '{}' ", + "empty map | [:] | '{}' ", + "single entry | [key1: value1] | '{\"key1\":\"value1\"}' ", + "two entries | [key1: value1, key2: value2] | '{\"key1\":\"value1\",\"key2\":\"value2\"}' ", + "quoted entries | [key1: va\"lu\"e1, ke\"y2: value2] | '{\"key1\":\"va\\\"lu\\\"e1\",\"ke\\\"y2\":\"value2\"}'" }) @ParameterizedTest(name = "test mapping to JSON object: {0}") @MethodSource("testMappingToJsonObjectArguments") @@ -94,12 +94,12 @@ void testMappingToMapFromNonObjectJson(String json) { } @TableTest({ - "Scenario | Input | Expected ", - "null input | | '[]' ", - "empty list | [] | '[]' ", - "single value | [value1] | '[\"value1\"]' ", - "two values | [value1, value2] | '[\"value1\",\"value2\"]' ", - "quoted values | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" + "Scenario | Input | Expected ", + "null input | | '[]' ", + "empty list | [] | '[]' ", + "single value | [value1] | '[\"value1\"]' ", + "two values | [value1, value2] | '[\"value1\",\"value2\"]' ", + "quoted values | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" }) @ParameterizedTest(name = "test mapping iterable to JSON array: {0}") void testMappingIterableToJsonArray(List input, String expected) throws IOException { @@ -111,12 +111,12 @@ void testMappingIterableToJsonArray(List input, String expected) throws } @TableTest({ - "Scenario | Input | Expected ", - "null input | | '[]' ", - "empty array | [] | '[]' ", - "single element | [value1] | '[\"value1\"]' ", - "two elements | [value1, value2] | '[\"value1\",\"value2\"]' ", - "escaped quotes | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" + "Scenario | Input | Expected ", + "null input | | '[]' ", + "empty array | [] | '[]' ", + "single element | [value1] | '[\"value1\"]' ", + "two elements | [value1, value2] | '[\"value1\",\"value2\"]' ", + "escaped quotes | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" }) @ParameterizedTest(name = "test mapping array to JSON array: {0}") void testMappingArrayToJsonArray(String ignoredScenario, String[] input, String expected) @@ -137,14 +137,14 @@ void testMappingToListFromEmptyJsonObject(String json) throws IOException { } @TableTest({ - "Scenario | input | expected ", - "null value | | '' ", - "empty string | '' | '' ", - "\\b | '\b' | '\"\\b\"'", - "\\t | '\t' | '\"\\t\"'", - "\\f | '\f' | '\"\\f\"'", - "a | 'a' | '\"a\"' ", - "/ | '/' | '\"\\/\"'" + "Scenario | input | expected ", + "null value | | '' ", + "empty string | '' | '' ", + "\\b | '\b' | '\"\\b\"'", + "\\t | '\t' | '\"\\t\"'", + "\\f | '\f' | '\"\\f\"'", + "a | 'a' | '\"a\"' ", + "/ | '/' | '\"\\/\"'" }) @ParameterizedTest(name = "test mapping to JSON string: {0}") @MethodSource("testMappingToJsonStringArguments") diff --git a/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java b/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java index 2499173f29f..cb2af17dcc0 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java @@ -19,12 +19,12 @@ class DDSpanIdTest { @TableTest({ - "scenario | stringId | expectedId ", - "zero | '0' | 0 ", - "one | '1' | 1 ", - "max | '18446744073709551615' | DDSpanId.MAX ", - "long max | '9223372036854775807' | Long.MAX_VALUE", - "long max plus one | '9223372036854775808' | Long.MIN_VALUE" + "scenario | stringId | expectedId ", + "zero | '0' | 0 ", + "one | '1' | 1 ", + "max | '18446744073709551615' | DDSpanId.MAX ", + "long max | '9223372036854775807' | Long.MAX_VALUE", + "long max plus one | '9223372036854775808' | Long.MIN_VALUE" }) @ParameterizedTest(name = "convert ids from/to String [{index}]") void convertIdsFromToString(String stringId, long expectedId) { @@ -51,15 +51,15 @@ void failOnIllegalString(String stringId) { } @TableTest({ - "scenario | hexId | expectedId ", - "zero | '0' | 0 ", - "one | '1' | 1 ", - "max | 'ffffffffffffffff' | DDSpanId.MAX ", - "long max | '7fffffffffffffff' | Long.MAX_VALUE ", - "long min | '8000000000000000' | Long.MIN_VALUE ", - "long min with leading zeros | '00008000000000000000' | Long.MIN_VALUE ", - "hex sample | 'cafebabe' | 3405691582 ", - "fifteen hex digits | '123456789abcdef' | 81985529216486895" + "scenario | hexId | expectedId ", + "zero | '0' | 0 ", + "one | '1' | 1 ", + "max | 'ffffffffffffffff' | DDSpanId.MAX ", + "long max | '7fffffffffffffff' | Long.MAX_VALUE ", + "long min | '8000000000000000' | Long.MIN_VALUE ", + "long min with leading zeros | '00008000000000000000' | Long.MIN_VALUE ", + "hex sample | 'cafebabe' | 3405691582 ", + "fifteen hex digits | '123456789abcdef' | 81985529216486895" }) @ParameterizedTest(name = "convert ids from/to hex String [{index}]") void convertIdsFromToHexString(String hexId, long expectedId) { @@ -77,17 +77,17 @@ void convertIdsFromToHexString(String hexId, long expectedId) { } @TableTest({ - "scenario | hexId | start | length | lowerCaseOnly | expectedId ", - "null input | | 1 | 1 | false | ", - "empty input | '' | 1 | 1 | false | ", - "negative start | '00' | -1 | 1 | false | ", - "zero length | '00' | 0 | 0 | false | ", - "single zero at index 0 | '00' | 0 | 1 | false | DDSpanId.ZERO", - "single zero at index 1 | '00' | 1 | 1 | false | DDSpanId.ZERO", - "single zero at index 1 duplicate | '00' | 1 | 1 | false | DDSpanId.ZERO", - "max lower-case | 'ffffffffffffffff' | 0 | 16 | true | DDSpanId.MAX ", - "upper-case rejected when lower-case only | 'ffffffffffffFfff' | 0 | 16 | true | ", - "upper-case accepted when lower disabled | 'ffffffffffffFfff' | 0 | 16 | false | DDSpanId.MAX " + "scenario | hexId | start | length | lowerCaseOnly | expectedId ", + "null input | | 1 | 1 | false | ", + "empty input | '' | 1 | 1 | false | ", + "negative start | '00' | -1 | 1 | false | ", + "zero length | '00' | 0 | 0 | false | ", + "single zero at index 0 | '00' | 0 | 1 | false | DDSpanId.ZERO", + "single zero at index 1 | '00' | 1 | 1 | false | DDSpanId.ZERO", + "single zero at index 1 duplicate | '00' | 1 | 1 | false | DDSpanId.ZERO", + "max lower-case | 'ffffffffffffffff' | 0 | 16 | true | DDSpanId.MAX ", + "upper-case rejected when lower-case only | 'ffffffffffffFfff' | 0 | 16 | true | ", + "upper-case accepted when lower disabled | 'ffffffffffffFfff' | 0 | 16 | false | DDSpanId.MAX " }) @ParameterizedTest(name = "convert ids from part of hex String [{index}]") void convertIdsFromPartOfHexString( diff --git a/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java index 3820b17c256..2cb4da9bf21 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java @@ -16,12 +16,12 @@ class DDTraceIdTest { @TableTest({ - "scenario | longId | expectedString | expectedHex ", - "zero | 0 | '0' | '00000000000000000000000000000000'", - "one | 1 | '1' | '00000000000000000000000000000001'", - "minus one | -1 | '18446744073709551615' | '0000000000000000ffffffffffffffff'", - "long max | Long.MAX_VALUE | '9223372036854775807' | '00000000000000007fffffffffffffff'", - "long min | Long.MIN_VALUE | '9223372036854775808' | '00000000000000008000000000000000'" + "scenario | longId | expectedString | expectedHex ", + "zero | 0 | '0' | '00000000000000000000000000000000'", + "one | 1 | '1' | '00000000000000000000000000000001'", + "minus one | -1 | '18446744073709551615' | '0000000000000000ffffffffffffffff'", + "long max | Long.MAX_VALUE | '9223372036854775807' | '00000000000000007fffffffffffffff'", + "long min | Long.MIN_VALUE | '9223372036854775808' | '00000000000000008000000000000000'" }) @ParameterizedTest(name = "convert 64-bit ids from/to long and check strings [{index}]") void convert64BitIdsFromToLongAndCheckStrings( @@ -39,12 +39,12 @@ void convert64BitIdsFromToLongAndCheckStrings( } @TableTest({ - "scenario | stringId | expectedId ", - "zero | '0' | DD64bTraceId.ZERO ", - "one | '1' | DD64bTraceId.ONE ", - "max | '18446744073709551615' | DD64bTraceId.MAX ", - "long max | '9223372036854775807' | DD64bTraceId.LONG_MAX", - "long max plus one | '9223372036854775808' | DD64bTraceId.LONG_MIN" + "scenario | stringId | expectedId ", + "zero | '0' | DD64bTraceId.ZERO ", + "one | '1' | DD64bTraceId.ONE ", + "max | '18446744073709551615' | DD64bTraceId.MAX ", + "long max | '9223372036854775807' | DD64bTraceId.LONG_MAX", + "long max plus one | '9223372036854775808' | DD64bTraceId.LONG_MIN" }) @ParameterizedTest(name = "convert 64-bit ids from/to String representation [{index}]") void convert64BitIdsFromToStringRepresentation(String stringId, DD64bTraceId expectedId) { @@ -71,15 +71,15 @@ void failParsingIllegal64BitIdStringRepresentation(String stringId) { } @TableTest({ - "scenario | hexId | expectedId ", - "zero | '0' | DD64bTraceId.ZERO ", - "one | '1' | DD64bTraceId.ONE ", - "max | 'ffffffffffffffff' | DD64bTraceId.MAX ", - "long max | '7fffffffffffffff' | DD64bTraceId.LONG_MAX", - "long min | '8000000000000000' | DD64bTraceId.LONG_MIN", - "long min with leading zeros | '00008000000000000000' | DD64bTraceId.LONG_MIN", - "hex sample | 'cafebabe' | DD64bTraceId.CAFEBABE", - "fifteen hex digits | '123456789abcdef' | DD64bTraceId.HEX " + "scenario | hexId | expectedId ", + "zero | '0' | DD64bTraceId.ZERO ", + "one | '1' | DD64bTraceId.ONE ", + "max | 'ffffffffffffffff' | DD64bTraceId.MAX ", + "long max | '7fffffffffffffff' | DD64bTraceId.LONG_MAX", + "long min | '8000000000000000' | DD64bTraceId.LONG_MIN", + "long min with leading zeros | '00008000000000000000' | DD64bTraceId.LONG_MIN", + "hex sample | 'cafebabe' | DD64bTraceId.CAFEBABE", + "fifteen hex digits | '123456789abcdef' | DD64bTraceId.HEX " }) @ParameterizedTest(name = "convert 64-bit ids from/to hex String representation [{index}]") void convert64BitIdsFromToHexStringRepresentation(String hexId, DD64bTraceId expectedId) { @@ -103,25 +103,25 @@ void failParsingIllegal64BitHexadecimalStringRepresentation(String hexId) { } @TableTest({ - "scenario | highOrderBits | lowOrderBits | hexId ", - "both long min | Long.MIN_VALUE | Long.MIN_VALUE | '80000000000000008000000000000000'", - "high long min low one | Long.MIN_VALUE | 1 | '80000000000000000000000000000001'", - "high long min low long max | Long.MIN_VALUE | Long.MAX_VALUE | '80000000000000007fffffffffffffff'", - "high one low long min | 1 | Long.MIN_VALUE | '00000000000000018000000000000000'", - "high one low one | 1 | 1 | '00000000000000010000000000000001'", - "high one low long max | 1 | Long.MAX_VALUE | '00000000000000017fffffffffffffff'", - "high long max low long min | Long.MAX_VALUE | Long.MIN_VALUE | '7fffffffffffffff8000000000000000'", - "high long max low one | Long.MAX_VALUE | 1 | '7fffffffffffffff0000000000000001'", - "high long max low long max | Long.MAX_VALUE | Long.MAX_VALUE | '7fffffffffffffff7fffffffffffffff'", - "all zeros length one | 0 | 0 | '0' ", - "all zeros length sixteen | 0 | 0 | '0000000000000000' ", - "all zeros length seventeen | 0 | 0 | '00000000000000000' ", - "all zeros length thirty-two | 0 | 0 | '00000000000000000000000000000000'", - "low fifteen | 0 | 15 | 'f' ", - "low minus one | 0 | -1 | 'ffffffffffffffff' ", - "high fifteen low minus one | 15 | -1 | 'fffffffffffffffff' ", - "all f | -1 | -1 | 'ffffffffffffffffffffffffffffffff'", - "hex literal | 1311768467463790320 | 1311768467463790320 | '123456789abcdef0123456789abcdef0'" + "scenario | highOrderBits | lowOrderBits | hexId ", + "both long min | Long.MIN_VALUE | Long.MIN_VALUE | '80000000000000008000000000000000'", + "high long min low one | Long.MIN_VALUE | 1 | '80000000000000000000000000000001'", + "high long min low long max | Long.MIN_VALUE | Long.MAX_VALUE | '80000000000000007fffffffffffffff'", + "high one low long min | 1 | Long.MIN_VALUE | '00000000000000018000000000000000'", + "high one low one | 1 | 1 | '00000000000000010000000000000001'", + "high one low long max | 1 | Long.MAX_VALUE | '00000000000000017fffffffffffffff'", + "high long max low long min | Long.MAX_VALUE | Long.MIN_VALUE | '7fffffffffffffff8000000000000000'", + "high long max low one | Long.MAX_VALUE | 1 | '7fffffffffffffff0000000000000001'", + "high long max low long max | Long.MAX_VALUE | Long.MAX_VALUE | '7fffffffffffffff7fffffffffffffff'", + "all zeros length one | 0 | 0 | '0' ", + "all zeros length sixteen | 0 | 0 | '0000000000000000' ", + "all zeros length seventeen | 0 | 0 | '00000000000000000' ", + "all zeros length thirty-two | 0 | 0 | '00000000000000000000000000000000'", + "low fifteen | 0 | 15 | 'f' ", + "low minus one | 0 | -1 | 'ffffffffffffffff' ", + "high fifteen low minus one | 15 | -1 | 'fffffffffffffffff' ", + "all f | -1 | -1 | 'ffffffffffffffffffffffffffffffff'", + "hex literal | 1311768467463790320 | 1311768467463790320 | '123456789abcdef0123456789abcdef0'" }) @ParameterizedTest( name = "convert 128-bit ids from/to hexadecimal String representation [{index}]") @@ -149,18 +149,18 @@ void failParsingIllegal128BitIdHexadecimalStringRepresentation(String hexId) { } @TableTest({ - "scenario | hexId | start | length | lowerCaseOnly", - "null string | | 0 | 0 | true ", - "empty string | '' | 0 | 0 | true ", - "out of bound length | '123456789abcdef0' | 0 | 17 | true ", - "out of bound end | '123456789abcdef0' | 7 | 10 | true ", - "out of bound start | '123456789abcdef0' | 17 | 0 | true ", - "invalid minus one | '-1' | 0 | 1 | true ", - "invalid minus a | '-a' | 0 | 1 | true ", - "invalid character | '123abcg' | 0 | 7 | true ", - "invalid upper case A | 'A' | 0 | 1 | true ", - "invalid upper case | '123ABC' | 0 | 6 | true ", - "too long | '111111111111111111111111111111111' | 0 | 33 | true " + "scenario | hexId | start | length | lowerCaseOnly", + "null string | | 0 | 0 | true ", + "empty string | '' | 0 | 0 | true ", + "out of bound length | '123456789abcdef0' | 0 | 17 | true ", + "out of bound end | '123456789abcdef0' | 7 | 10 | true ", + "out of bound start | '123456789abcdef0' | 17 | 0 | true ", + "invalid minus one | '-1' | 0 | 1 | true ", + "invalid minus a | '-a' | 0 | 1 | true ", + "invalid character | '123abcg' | 0 | 7 | true ", + "invalid upper case A | 'A' | 0 | 1 | true ", + "invalid upper case | '123ABC' | 0 | 6 | true ", + "too long | '111111111111111111111111111111111' | 0 | 33 | true " }) @ParameterizedTest( name = diff --git a/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java b/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java index e5b5a8efcf9..d01bcc6082a 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java @@ -18,9 +18,9 @@ class IdGenerationStrategyTest { @TableTest({ - "scenario | traceId128BitGenerationEnabled | strategyName ", - "strategies-64-bit | false | {RANDOM, SEQUENTIAL, SECURE_RANDOM}", - "strategies-128-bit | true | {RANDOM, SEQUENTIAL, SECURE_RANDOM}" + "scenario | traceId128BitGenerationEnabled | strategyName ", + "strategies-64-bit | false | {RANDOM, SEQUENTIAL, SECURE_RANDOM}", + "strategies-128-bit | true | {RANDOM, SEQUENTIAL, SECURE_RANDOM}" }) @ParameterizedTest(name = "generate id with {1} and {0} bits") void generateIdWithStrategyAndBitSize( diff --git a/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java b/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java index 14eb065607d..d3cf14a8624 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java @@ -8,10 +8,10 @@ class HexStringUtilsTest { @TableTest({ - "scenario | highOrderBits | lowOrderBits | size ", - "zero | 0 | 0 | {10, 16, 20, 32, 40}", - "one-two | 1 | 2 | {10, 16, 20, 32, 40}", - "large | 6536977903480360123 | 3270264562721133536 | {10, 16, 20, 32, 40}" + "scenario | highOrderBits | lowOrderBits | size ", + "zero | 0 | 0 | {10, 16, 20, 32, 40}", + "one-two | 1 | 2 | {10, 16, 20, 32, 40}", + "large | 6536977903480360123 | 3270264562721133536 | {10, 16, 20, 32, 40}" }) @ParameterizedTest(name = "test hexadecimal String representations high={0} low={1} size={2}") void testHexadecimalStringRepresentations(long highOrderBits, long lowOrderBits, int size) { 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..417bbedc284 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,8 @@ public static Sampler forConfig(final Config config, final TraceConfig traceConf log.error("Invalid sampler configuration. Using AllSampler", e); sampler = new AllSampler(); } + } else if (config.isTraceOtlpExporterEnabled()) { + sampler = new ParentBasedAlwaysOnSampler(); } 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/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy new file mode 100644 index 00000000000..9b668fe047f --- /dev/null +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy @@ -0,0 +1,114 @@ +package datadog.trace.common.sampling + +import datadog.trace.api.DDTraceId +import datadog.trace.common.writer.ListWriter +import datadog.trace.common.writer.RemoteResponseListener +import datadog.trace.core.propagation.ExtractedContext +import datadog.trace.core.propagation.PropagationTags +import datadog.trace.core.test.DDCoreSpecification + +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 + +class ParentBasedAlwaysOnSamplerTest extends DDCoreSpecification { + + def writer = new ListWriter() + + def "always samples spans"() { + setup: + def sampler = new ParentBasedAlwaysOnSampler() + def tracer = tracerBuilder().writer(writer).sampler(sampler).build() + + when: + def span = tracer.buildSpan("test").start() + + then: + sampler.sample(span) == true + + cleanup: + span.finish() + tracer.close() + } + + def "sets sampling priority to SAMPLER_KEEP"() { + setup: + def sampler = new ParentBasedAlwaysOnSampler() + def tracer = tracerBuilder().writer(writer).sampler(sampler).build() + + when: + def span = tracer.buildSpan("test").start() + sampler.setSamplingPriority(span) + + then: + span.getSamplingPriority() == SAMPLER_KEEP + + cleanup: + span.finish() + tracer.close() + } + + def "child span inherits sampling priority from local parent"() { + setup: + def sampler = new ParentBasedAlwaysOnSampler() + def tracer = tracerBuilder().writer(writer).sampler(sampler).build() + + when: + def rootSpan = tracer.buildSpan("root").start() + sampler.setSamplingPriority(rootSpan) + def childSpan = tracer.buildSpan("child").asChildOf(rootSpan).start() + + then: + rootSpan.getSamplingPriority() == SAMPLER_KEEP + childSpan.getSamplingPriority() == SAMPLER_KEEP + + cleanup: + childSpan.finish() + rootSpan.finish() + tracer.close() + } + + def "child span inherits sampling decision from remote parent"() { + setup: + def sampler = new ParentBasedAlwaysOnSampler() + def tracer = tracerBuilder().writer(writer).sampler(sampler).build() + def extractedContext = new ExtractedContext( + DDTraceId.ONE, 2, parentPriority, null, + PropagationTags.factory().empty(), DATADOG) + + when: + def span = tracer.buildSpan("child").asChildOf(extractedContext).start() + + then: + span.getSamplingPriority() == parentPriority + + cleanup: + span.finish() + tracer.close() + + where: + parentPriority | _ + SAMPLER_KEEP | _ + SAMPLER_DROP | _ + USER_KEEP | _ + USER_DROP | _ + } + + def "is not a RemoteResponseListener"() { + setup: + def sampler = new ParentBasedAlwaysOnSampler() + + expect: + !(sampler instanceof RemoteResponseListener) + } + + def "implements PrioritySampler"() { + setup: + def sampler = new ParentBasedAlwaysOnSampler() + + expect: + sampler instanceof PrioritySampler + } +} diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/SamplerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/SamplerTest.groovy index 706f45ea43f..04c44a97878 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/SamplerTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/SamplerTest.groovy @@ -66,4 +66,53 @@ class SamplerTest extends DDSpecification{ then: !(sampler instanceof AsmStandaloneSampler) } + + void "test that ParentBasedAlwaysOnSampler is selected when otlp traces exporter enabled"() { + setup: + System.setProperty("dd.trace.otel.exporter", "otlp") + Config config = new Config() + + when: + Sampler sampler = Sampler.Builder.forConfig(config, null) + + then: + sampler instanceof ParentBasedAlwaysOnSampler + } + + void "test that ParentBasedAlwaysOnSampler is not selected when otlp traces exporter not set"() { + setup: + Config config = new Config() + + when: + Sampler sampler = Sampler.Builder.forConfig(config, null) + + then: + !(sampler instanceof ParentBasedAlwaysOnSampler) + } + + void "test that user-defined sampling rules take precedence over otlp default sampler"() { + setup: + System.setProperty("dd.trace.otel.exporter", "otlp") + System.setProperty("dd.trace.sample.rate", "0.5") + Config config = new Config() + + when: + Sampler sampler = Sampler.Builder.forConfig(config, null) + + then: + sampler instanceof RuleBasedTraceSampler + } + + void "test that DD_TRACE_SAMPLING_RULES takes precedence over otlp default sampler"() { + setup: + System.setProperty("dd.trace.otel.exporter", "otlp") + System.setProperty("dd.trace.sampling.rules", '[{"sample_rate": 0.5}]') + Config config = new Config() + + when: + Sampler sampler = Sampler.Builder.forConfig(config, null) + + then: + sampler instanceof RuleBasedTraceSampler + } } From cee3a77742a0a5d4d7f715a66537ca90e7cbf016 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 20 Mar 2026 12:54:09 -0400 Subject: [PATCH 2/4] restore superfluous test file formatting back to master --- .../java/datadog/json/JsonMapperTest.java | 52 ++++----- .../java/datadog/trace/api/DDSpanIdTest.java | 52 ++++----- .../java/datadog/trace/api/DDTraceIdTest.java | 104 +++++++++--------- .../trace/api/IdGenerationStrategyTest.java | 6 +- .../api/internal/util/HexStringUtilsTest.java | 8 +- 5 files changed, 111 insertions(+), 111 deletions(-) diff --git a/components/json/src/test/java/datadog/json/JsonMapperTest.java b/components/json/src/test/java/datadog/json/JsonMapperTest.java index eef94bd5032..926702881bc 100644 --- a/components/json/src/test/java/datadog/json/JsonMapperTest.java +++ b/components/json/src/test/java/datadog/json/JsonMapperTest.java @@ -24,12 +24,12 @@ class JsonMapperTest { @TableTest({ - "Scenario | Input | Expected ", - "null input | | '{}' ", - "empty map | [:] | '{}' ", - "single entry | [key1: value1] | '{\"key1\":\"value1\"}' ", - "two entries | [key1: value1, key2: value2] | '{\"key1\":\"value1\",\"key2\":\"value2\"}' ", - "quoted entries | [key1: va\"lu\"e1, ke\"y2: value2] | '{\"key1\":\"va\\\"lu\\\"e1\",\"ke\\\"y2\":\"value2\"}'" + "Scenario | Input | Expected ", + "null input | | '{}' ", + "empty map | [:] | '{}' ", + "single entry | [key1: value1] | '{\"key1\":\"value1\"}' ", + "two entries | [key1: value1, key2: value2] | '{\"key1\":\"value1\",\"key2\":\"value2\"}' ", + "quoted entries | [key1: va\"lu\"e1, ke\"y2: value2] | '{\"key1\":\"va\\\"lu\\\"e1\",\"ke\\\"y2\":\"value2\"}'" }) @ParameterizedTest(name = "test mapping to JSON object: {0}") @MethodSource("testMappingToJsonObjectArguments") @@ -94,12 +94,12 @@ void testMappingToMapFromNonObjectJson(String json) { } @TableTest({ - "Scenario | Input | Expected ", - "null input | | '[]' ", - "empty list | [] | '[]' ", - "single value | [value1] | '[\"value1\"]' ", - "two values | [value1, value2] | '[\"value1\",\"value2\"]' ", - "quoted values | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" + "Scenario | Input | Expected ", + "null input | | '[]' ", + "empty list | [] | '[]' ", + "single value | [value1] | '[\"value1\"]' ", + "two values | [value1, value2] | '[\"value1\",\"value2\"]' ", + "quoted values | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" }) @ParameterizedTest(name = "test mapping iterable to JSON array: {0}") void testMappingIterableToJsonArray(List input, String expected) throws IOException { @@ -111,12 +111,12 @@ void testMappingIterableToJsonArray(List input, String expected) throws } @TableTest({ - "Scenario | Input | Expected ", - "null input | | '[]' ", - "empty array | [] | '[]' ", - "single element | [value1] | '[\"value1\"]' ", - "two elements | [value1, value2] | '[\"value1\",\"value2\"]' ", - "escaped quotes | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" + "Scenario | Input | Expected ", + "null input | | '[]' ", + "empty array | [] | '[]' ", + "single element | [value1] | '[\"value1\"]' ", + "two elements | [value1, value2] | '[\"value1\",\"value2\"]' ", + "escaped quotes | [va\"lu\"e1, value2] | '[\"va\\\"lu\\\"e1\",\"value2\"]'" }) @ParameterizedTest(name = "test mapping array to JSON array: {0}") void testMappingArrayToJsonArray(String ignoredScenario, String[] input, String expected) @@ -137,14 +137,14 @@ void testMappingToListFromEmptyJsonObject(String json) throws IOException { } @TableTest({ - "Scenario | input | expected ", - "null value | | '' ", - "empty string | '' | '' ", - "\\b | '\b' | '\"\\b\"'", - "\\t | '\t' | '\"\\t\"'", - "\\f | '\f' | '\"\\f\"'", - "a | 'a' | '\"a\"' ", - "/ | '/' | '\"\\/\"'" + "Scenario | input | expected ", + "null value | | '' ", + "empty string | '' | '' ", + "\\b | '\b' | '\"\\b\"'", + "\\t | '\t' | '\"\\t\"'", + "\\f | '\f' | '\"\\f\"'", + "a | 'a' | '\"a\"' ", + "/ | '/' | '\"\\/\"'" }) @ParameterizedTest(name = "test mapping to JSON string: {0}") @MethodSource("testMappingToJsonStringArguments") diff --git a/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java b/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java index cb2af17dcc0..2499173f29f 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/DDSpanIdTest.java @@ -19,12 +19,12 @@ class DDSpanIdTest { @TableTest({ - "scenario | stringId | expectedId ", - "zero | '0' | 0 ", - "one | '1' | 1 ", - "max | '18446744073709551615' | DDSpanId.MAX ", - "long max | '9223372036854775807' | Long.MAX_VALUE", - "long max plus one | '9223372036854775808' | Long.MIN_VALUE" + "scenario | stringId | expectedId ", + "zero | '0' | 0 ", + "one | '1' | 1 ", + "max | '18446744073709551615' | DDSpanId.MAX ", + "long max | '9223372036854775807' | Long.MAX_VALUE", + "long max plus one | '9223372036854775808' | Long.MIN_VALUE" }) @ParameterizedTest(name = "convert ids from/to String [{index}]") void convertIdsFromToString(String stringId, long expectedId) { @@ -51,15 +51,15 @@ void failOnIllegalString(String stringId) { } @TableTest({ - "scenario | hexId | expectedId ", - "zero | '0' | 0 ", - "one | '1' | 1 ", - "max | 'ffffffffffffffff' | DDSpanId.MAX ", - "long max | '7fffffffffffffff' | Long.MAX_VALUE ", - "long min | '8000000000000000' | Long.MIN_VALUE ", - "long min with leading zeros | '00008000000000000000' | Long.MIN_VALUE ", - "hex sample | 'cafebabe' | 3405691582 ", - "fifteen hex digits | '123456789abcdef' | 81985529216486895" + "scenario | hexId | expectedId ", + "zero | '0' | 0 ", + "one | '1' | 1 ", + "max | 'ffffffffffffffff' | DDSpanId.MAX ", + "long max | '7fffffffffffffff' | Long.MAX_VALUE ", + "long min | '8000000000000000' | Long.MIN_VALUE ", + "long min with leading zeros | '00008000000000000000' | Long.MIN_VALUE ", + "hex sample | 'cafebabe' | 3405691582 ", + "fifteen hex digits | '123456789abcdef' | 81985529216486895" }) @ParameterizedTest(name = "convert ids from/to hex String [{index}]") void convertIdsFromToHexString(String hexId, long expectedId) { @@ -77,17 +77,17 @@ void convertIdsFromToHexString(String hexId, long expectedId) { } @TableTest({ - "scenario | hexId | start | length | lowerCaseOnly | expectedId ", - "null input | | 1 | 1 | false | ", - "empty input | '' | 1 | 1 | false | ", - "negative start | '00' | -1 | 1 | false | ", - "zero length | '00' | 0 | 0 | false | ", - "single zero at index 0 | '00' | 0 | 1 | false | DDSpanId.ZERO", - "single zero at index 1 | '00' | 1 | 1 | false | DDSpanId.ZERO", - "single zero at index 1 duplicate | '00' | 1 | 1 | false | DDSpanId.ZERO", - "max lower-case | 'ffffffffffffffff' | 0 | 16 | true | DDSpanId.MAX ", - "upper-case rejected when lower-case only | 'ffffffffffffFfff' | 0 | 16 | true | ", - "upper-case accepted when lower disabled | 'ffffffffffffFfff' | 0 | 16 | false | DDSpanId.MAX " + "scenario | hexId | start | length | lowerCaseOnly | expectedId ", + "null input | | 1 | 1 | false | ", + "empty input | '' | 1 | 1 | false | ", + "negative start | '00' | -1 | 1 | false | ", + "zero length | '00' | 0 | 0 | false | ", + "single zero at index 0 | '00' | 0 | 1 | false | DDSpanId.ZERO", + "single zero at index 1 | '00' | 1 | 1 | false | DDSpanId.ZERO", + "single zero at index 1 duplicate | '00' | 1 | 1 | false | DDSpanId.ZERO", + "max lower-case | 'ffffffffffffffff' | 0 | 16 | true | DDSpanId.MAX ", + "upper-case rejected when lower-case only | 'ffffffffffffFfff' | 0 | 16 | true | ", + "upper-case accepted when lower disabled | 'ffffffffffffFfff' | 0 | 16 | false | DDSpanId.MAX " }) @ParameterizedTest(name = "convert ids from part of hex String [{index}]") void convertIdsFromPartOfHexString( diff --git a/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java index 2cb4da9bf21..3820b17c256 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/DDTraceIdTest.java @@ -16,12 +16,12 @@ class DDTraceIdTest { @TableTest({ - "scenario | longId | expectedString | expectedHex ", - "zero | 0 | '0' | '00000000000000000000000000000000'", - "one | 1 | '1' | '00000000000000000000000000000001'", - "minus one | -1 | '18446744073709551615' | '0000000000000000ffffffffffffffff'", - "long max | Long.MAX_VALUE | '9223372036854775807' | '00000000000000007fffffffffffffff'", - "long min | Long.MIN_VALUE | '9223372036854775808' | '00000000000000008000000000000000'" + "scenario | longId | expectedString | expectedHex ", + "zero | 0 | '0' | '00000000000000000000000000000000'", + "one | 1 | '1' | '00000000000000000000000000000001'", + "minus one | -1 | '18446744073709551615' | '0000000000000000ffffffffffffffff'", + "long max | Long.MAX_VALUE | '9223372036854775807' | '00000000000000007fffffffffffffff'", + "long min | Long.MIN_VALUE | '9223372036854775808' | '00000000000000008000000000000000'" }) @ParameterizedTest(name = "convert 64-bit ids from/to long and check strings [{index}]") void convert64BitIdsFromToLongAndCheckStrings( @@ -39,12 +39,12 @@ void convert64BitIdsFromToLongAndCheckStrings( } @TableTest({ - "scenario | stringId | expectedId ", - "zero | '0' | DD64bTraceId.ZERO ", - "one | '1' | DD64bTraceId.ONE ", - "max | '18446744073709551615' | DD64bTraceId.MAX ", - "long max | '9223372036854775807' | DD64bTraceId.LONG_MAX", - "long max plus one | '9223372036854775808' | DD64bTraceId.LONG_MIN" + "scenario | stringId | expectedId ", + "zero | '0' | DD64bTraceId.ZERO ", + "one | '1' | DD64bTraceId.ONE ", + "max | '18446744073709551615' | DD64bTraceId.MAX ", + "long max | '9223372036854775807' | DD64bTraceId.LONG_MAX", + "long max plus one | '9223372036854775808' | DD64bTraceId.LONG_MIN" }) @ParameterizedTest(name = "convert 64-bit ids from/to String representation [{index}]") void convert64BitIdsFromToStringRepresentation(String stringId, DD64bTraceId expectedId) { @@ -71,15 +71,15 @@ void failParsingIllegal64BitIdStringRepresentation(String stringId) { } @TableTest({ - "scenario | hexId | expectedId ", - "zero | '0' | DD64bTraceId.ZERO ", - "one | '1' | DD64bTraceId.ONE ", - "max | 'ffffffffffffffff' | DD64bTraceId.MAX ", - "long max | '7fffffffffffffff' | DD64bTraceId.LONG_MAX", - "long min | '8000000000000000' | DD64bTraceId.LONG_MIN", - "long min with leading zeros | '00008000000000000000' | DD64bTraceId.LONG_MIN", - "hex sample | 'cafebabe' | DD64bTraceId.CAFEBABE", - "fifteen hex digits | '123456789abcdef' | DD64bTraceId.HEX " + "scenario | hexId | expectedId ", + "zero | '0' | DD64bTraceId.ZERO ", + "one | '1' | DD64bTraceId.ONE ", + "max | 'ffffffffffffffff' | DD64bTraceId.MAX ", + "long max | '7fffffffffffffff' | DD64bTraceId.LONG_MAX", + "long min | '8000000000000000' | DD64bTraceId.LONG_MIN", + "long min with leading zeros | '00008000000000000000' | DD64bTraceId.LONG_MIN", + "hex sample | 'cafebabe' | DD64bTraceId.CAFEBABE", + "fifteen hex digits | '123456789abcdef' | DD64bTraceId.HEX " }) @ParameterizedTest(name = "convert 64-bit ids from/to hex String representation [{index}]") void convert64BitIdsFromToHexStringRepresentation(String hexId, DD64bTraceId expectedId) { @@ -103,25 +103,25 @@ void failParsingIllegal64BitHexadecimalStringRepresentation(String hexId) { } @TableTest({ - "scenario | highOrderBits | lowOrderBits | hexId ", - "both long min | Long.MIN_VALUE | Long.MIN_VALUE | '80000000000000008000000000000000'", - "high long min low one | Long.MIN_VALUE | 1 | '80000000000000000000000000000001'", - "high long min low long max | Long.MIN_VALUE | Long.MAX_VALUE | '80000000000000007fffffffffffffff'", - "high one low long min | 1 | Long.MIN_VALUE | '00000000000000018000000000000000'", - "high one low one | 1 | 1 | '00000000000000010000000000000001'", - "high one low long max | 1 | Long.MAX_VALUE | '00000000000000017fffffffffffffff'", - "high long max low long min | Long.MAX_VALUE | Long.MIN_VALUE | '7fffffffffffffff8000000000000000'", - "high long max low one | Long.MAX_VALUE | 1 | '7fffffffffffffff0000000000000001'", - "high long max low long max | Long.MAX_VALUE | Long.MAX_VALUE | '7fffffffffffffff7fffffffffffffff'", - "all zeros length one | 0 | 0 | '0' ", - "all zeros length sixteen | 0 | 0 | '0000000000000000' ", - "all zeros length seventeen | 0 | 0 | '00000000000000000' ", - "all zeros length thirty-two | 0 | 0 | '00000000000000000000000000000000'", - "low fifteen | 0 | 15 | 'f' ", - "low minus one | 0 | -1 | 'ffffffffffffffff' ", - "high fifteen low minus one | 15 | -1 | 'fffffffffffffffff' ", - "all f | -1 | -1 | 'ffffffffffffffffffffffffffffffff'", - "hex literal | 1311768467463790320 | 1311768467463790320 | '123456789abcdef0123456789abcdef0'" + "scenario | highOrderBits | lowOrderBits | hexId ", + "both long min | Long.MIN_VALUE | Long.MIN_VALUE | '80000000000000008000000000000000'", + "high long min low one | Long.MIN_VALUE | 1 | '80000000000000000000000000000001'", + "high long min low long max | Long.MIN_VALUE | Long.MAX_VALUE | '80000000000000007fffffffffffffff'", + "high one low long min | 1 | Long.MIN_VALUE | '00000000000000018000000000000000'", + "high one low one | 1 | 1 | '00000000000000010000000000000001'", + "high one low long max | 1 | Long.MAX_VALUE | '00000000000000017fffffffffffffff'", + "high long max low long min | Long.MAX_VALUE | Long.MIN_VALUE | '7fffffffffffffff8000000000000000'", + "high long max low one | Long.MAX_VALUE | 1 | '7fffffffffffffff0000000000000001'", + "high long max low long max | Long.MAX_VALUE | Long.MAX_VALUE | '7fffffffffffffff7fffffffffffffff'", + "all zeros length one | 0 | 0 | '0' ", + "all zeros length sixteen | 0 | 0 | '0000000000000000' ", + "all zeros length seventeen | 0 | 0 | '00000000000000000' ", + "all zeros length thirty-two | 0 | 0 | '00000000000000000000000000000000'", + "low fifteen | 0 | 15 | 'f' ", + "low minus one | 0 | -1 | 'ffffffffffffffff' ", + "high fifteen low minus one | 15 | -1 | 'fffffffffffffffff' ", + "all f | -1 | -1 | 'ffffffffffffffffffffffffffffffff'", + "hex literal | 1311768467463790320 | 1311768467463790320 | '123456789abcdef0123456789abcdef0'" }) @ParameterizedTest( name = "convert 128-bit ids from/to hexadecimal String representation [{index}]") @@ -149,18 +149,18 @@ void failParsingIllegal128BitIdHexadecimalStringRepresentation(String hexId) { } @TableTest({ - "scenario | hexId | start | length | lowerCaseOnly", - "null string | | 0 | 0 | true ", - "empty string | '' | 0 | 0 | true ", - "out of bound length | '123456789abcdef0' | 0 | 17 | true ", - "out of bound end | '123456789abcdef0' | 7 | 10 | true ", - "out of bound start | '123456789abcdef0' | 17 | 0 | true ", - "invalid minus one | '-1' | 0 | 1 | true ", - "invalid minus a | '-a' | 0 | 1 | true ", - "invalid character | '123abcg' | 0 | 7 | true ", - "invalid upper case A | 'A' | 0 | 1 | true ", - "invalid upper case | '123ABC' | 0 | 6 | true ", - "too long | '111111111111111111111111111111111' | 0 | 33 | true " + "scenario | hexId | start | length | lowerCaseOnly", + "null string | | 0 | 0 | true ", + "empty string | '' | 0 | 0 | true ", + "out of bound length | '123456789abcdef0' | 0 | 17 | true ", + "out of bound end | '123456789abcdef0' | 7 | 10 | true ", + "out of bound start | '123456789abcdef0' | 17 | 0 | true ", + "invalid minus one | '-1' | 0 | 1 | true ", + "invalid minus a | '-a' | 0 | 1 | true ", + "invalid character | '123abcg' | 0 | 7 | true ", + "invalid upper case A | 'A' | 0 | 1 | true ", + "invalid upper case | '123ABC' | 0 | 6 | true ", + "too long | '111111111111111111111111111111111' | 0 | 33 | true " }) @ParameterizedTest( name = diff --git a/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java b/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java index d01bcc6082a..e5b5a8efcf9 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/IdGenerationStrategyTest.java @@ -18,9 +18,9 @@ class IdGenerationStrategyTest { @TableTest({ - "scenario | traceId128BitGenerationEnabled | strategyName ", - "strategies-64-bit | false | {RANDOM, SEQUENTIAL, SECURE_RANDOM}", - "strategies-128-bit | true | {RANDOM, SEQUENTIAL, SECURE_RANDOM}" + "scenario | traceId128BitGenerationEnabled | strategyName ", + "strategies-64-bit | false | {RANDOM, SEQUENTIAL, SECURE_RANDOM}", + "strategies-128-bit | true | {RANDOM, SEQUENTIAL, SECURE_RANDOM}" }) @ParameterizedTest(name = "generate id with {1} and {0} bits") void generateIdWithStrategyAndBitSize( diff --git a/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java b/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java index d3cf14a8624..14eb065607d 100644 --- a/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java +++ b/dd-trace-api/src/test/java/datadog/trace/api/internal/util/HexStringUtilsTest.java @@ -8,10 +8,10 @@ class HexStringUtilsTest { @TableTest({ - "scenario | highOrderBits | lowOrderBits | size ", - "zero | 0 | 0 | {10, 16, 20, 32, 40}", - "one-two | 1 | 2 | {10, 16, 20, 32, 40}", - "large | 6536977903480360123 | 3270264562721133536 | {10, 16, 20, 32, 40}" + "scenario | highOrderBits | lowOrderBits | size ", + "zero | 0 | 0 | {10, 16, 20, 32, 40}", + "one-two | 1 | 2 | {10, 16, 20, 32, 40}", + "large | 6536977903480360123 | 3270264562721133536 | {10, 16, 20, 32, 40}" }) @ParameterizedTest(name = "test hexadecimal String representations high={0} low={1} size={2}") void testHexadecimalStringRepresentations(long highOrderBits, long lowOrderBits, int size) { From fc645af9569987e86c1223b2dae435cc3bbe4fd2 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 20 Mar 2026 13:02:37 -0400 Subject: [PATCH 3/4] Remove logic for actually enabling ParentBasedSampler --- .../trace/common/sampling/Sampler.java | 3 +- .../trace/common/sampling/SamplerTest.groovy | 49 ------------------- 2 files changed, 1 insertion(+), 51 deletions(-) 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 417bbedc284..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,8 +80,7 @@ public static Sampler forConfig(final Config config, final TraceConfig traceConf log.error("Invalid sampler configuration. Using AllSampler", e); sampler = new AllSampler(); } - } else if (config.isTraceOtlpExporterEnabled()) { - sampler = new ParentBasedAlwaysOnSampler(); + // 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/groovy/datadog/trace/common/sampling/SamplerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/SamplerTest.groovy index 04c44a97878..706f45ea43f 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/SamplerTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/SamplerTest.groovy @@ -66,53 +66,4 @@ class SamplerTest extends DDSpecification{ then: !(sampler instanceof AsmStandaloneSampler) } - - void "test that ParentBasedAlwaysOnSampler is selected when otlp traces exporter enabled"() { - setup: - System.setProperty("dd.trace.otel.exporter", "otlp") - Config config = new Config() - - when: - Sampler sampler = Sampler.Builder.forConfig(config, null) - - then: - sampler instanceof ParentBasedAlwaysOnSampler - } - - void "test that ParentBasedAlwaysOnSampler is not selected when otlp traces exporter not set"() { - setup: - Config config = new Config() - - when: - Sampler sampler = Sampler.Builder.forConfig(config, null) - - then: - !(sampler instanceof ParentBasedAlwaysOnSampler) - } - - void "test that user-defined sampling rules take precedence over otlp default sampler"() { - setup: - System.setProperty("dd.trace.otel.exporter", "otlp") - System.setProperty("dd.trace.sample.rate", "0.5") - Config config = new Config() - - when: - Sampler sampler = Sampler.Builder.forConfig(config, null) - - then: - sampler instanceof RuleBasedTraceSampler - } - - void "test that DD_TRACE_SAMPLING_RULES takes precedence over otlp default sampler"() { - setup: - System.setProperty("dd.trace.otel.exporter", "otlp") - System.setProperty("dd.trace.sampling.rules", '[{"sample_rate": 0.5}]') - Config config = new Config() - - when: - Sampler sampler = Sampler.Builder.forConfig(config, null) - - then: - sampler instanceof RuleBasedTraceSampler - } } From f3bc0c8bb14f32b5b5599f3d1d252b5d24715cea Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 20 Mar 2026 14:24:22 -0400 Subject: [PATCH 4/4] migrate groovy -> junit --- .../ParentBasedAlwaysOnSamplerTest.groovy | 114 ---------------- .../ParentBasedAlwaysOnSamplerTest.java | 124 ++++++++++++++++++ 2 files changed, 124 insertions(+), 114 deletions(-) delete mode 100644 dd-trace-core/src/test/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy create mode 100644 dd-trace-core/src/test/java/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.java diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy deleted file mode 100644 index 9b668fe047f..00000000000 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/sampling/ParentBasedAlwaysOnSamplerTest.groovy +++ /dev/null @@ -1,114 +0,0 @@ -package datadog.trace.common.sampling - -import datadog.trace.api.DDTraceId -import datadog.trace.common.writer.ListWriter -import datadog.trace.common.writer.RemoteResponseListener -import datadog.trace.core.propagation.ExtractedContext -import datadog.trace.core.propagation.PropagationTags -import datadog.trace.core.test.DDCoreSpecification - -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 - -class ParentBasedAlwaysOnSamplerTest extends DDCoreSpecification { - - def writer = new ListWriter() - - def "always samples spans"() { - setup: - def sampler = new ParentBasedAlwaysOnSampler() - def tracer = tracerBuilder().writer(writer).sampler(sampler).build() - - when: - def span = tracer.buildSpan("test").start() - - then: - sampler.sample(span) == true - - cleanup: - span.finish() - tracer.close() - } - - def "sets sampling priority to SAMPLER_KEEP"() { - setup: - def sampler = new ParentBasedAlwaysOnSampler() - def tracer = tracerBuilder().writer(writer).sampler(sampler).build() - - when: - def span = tracer.buildSpan("test").start() - sampler.setSamplingPriority(span) - - then: - span.getSamplingPriority() == SAMPLER_KEEP - - cleanup: - span.finish() - tracer.close() - } - - def "child span inherits sampling priority from local parent"() { - setup: - def sampler = new ParentBasedAlwaysOnSampler() - def tracer = tracerBuilder().writer(writer).sampler(sampler).build() - - when: - def rootSpan = tracer.buildSpan("root").start() - sampler.setSamplingPriority(rootSpan) - def childSpan = tracer.buildSpan("child").asChildOf(rootSpan).start() - - then: - rootSpan.getSamplingPriority() == SAMPLER_KEEP - childSpan.getSamplingPriority() == SAMPLER_KEEP - - cleanup: - childSpan.finish() - rootSpan.finish() - tracer.close() - } - - def "child span inherits sampling decision from remote parent"() { - setup: - def sampler = new ParentBasedAlwaysOnSampler() - def tracer = tracerBuilder().writer(writer).sampler(sampler).build() - def extractedContext = new ExtractedContext( - DDTraceId.ONE, 2, parentPriority, null, - PropagationTags.factory().empty(), DATADOG) - - when: - def span = tracer.buildSpan("child").asChildOf(extractedContext).start() - - then: - span.getSamplingPriority() == parentPriority - - cleanup: - span.finish() - tracer.close() - - where: - parentPriority | _ - SAMPLER_KEEP | _ - SAMPLER_DROP | _ - USER_KEEP | _ - USER_DROP | _ - } - - def "is not a RemoteResponseListener"() { - setup: - def sampler = new ParentBasedAlwaysOnSampler() - - expect: - !(sampler instanceof RemoteResponseListener) - } - - def "implements PrioritySampler"() { - setup: - def sampler = new ParentBasedAlwaysOnSampler() - - expect: - sampler instanceof PrioritySampler - } -} 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); + } +}