From 87f61d7f2ee413c992aaee5b2083689db2342e11 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 27 Aug 2026 10:19:59 +0200 Subject: [PATCH 01/15] refactor(propagation): parse otel tracestate members --- .../propagation/ptags/OtelTraceState.java | 30 ++++ .../core/propagation/ptags/PTagsFactory.java | 15 ++ .../core/propagation/ptags/W3CPTagsCodec.java | 152 +++++++++++++----- 3 files changed, 160 insertions(+), 37 deletions(-) create mode 100644 dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java new file mode 100644 index 00000000000..3b8e837b5b9 --- /dev/null +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java @@ -0,0 +1,30 @@ +package datadog.trace.core.propagation.ptags; + +final class OtelTraceState { + private final String value; + private final int inheritedPosition; + + private OtelTraceState(String value, int inheritedPosition) { + this.value = value; + this.inheritedPosition = inheritedPosition; + } + + static OtelTraceState parse(String raw, int inheritedPosition) { + if (raw == null || raw.isEmpty()) { + return null; + } + return new OtelTraceState(raw, inheritedPosition); + } + + String getValue() { + return value; + } + + int length() { + return value.length(); + } + + int getInheritedPosition() { + return inheritedPosition; + } +} diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java index 0b5184d448a..1661398c819 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java @@ -112,6 +112,8 @@ static class PTags extends PropagationTags { private volatile TagValue orgPropagationMarkerTagValue; + private volatile OtelTraceState otelTraceState; + // Static cache for the most-recently-seen rate → TagValue. In steady state a service uses one // rate, so this eliminates the char[] + String allocation on every new PTags instance. // Writes are benign-racy: two threads computing the same rate produce equal TagValues. @@ -540,7 +542,20 @@ public String getW3CTracestate() { @Override public void updateW3CTracestate(String tracestate) { + clearCachedHeader(W3C); this.tracestate = tracestate; + setOtelTraceState(W3CPTagsCodec.extractOtelTraceState(tracestate)); + } + + OtelTraceState getOtelTraceState() { + return otelTraceState; + } + + void setOtelTraceState(OtelTraceState otelTraceState) { + if (this.otelTraceState != otelTraceState) { + this.otelTraceState = otelTraceState; + clearCachedHeader(W3C); + } } String getError() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index c0018544188..d56a0076017 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -21,6 +21,7 @@ public class W3CPTagsCodec extends PTagsCodec { private static final int MAX_HEADER_SIZE = 256; private static final String DATADOG_MEMBER_KEY = "dd="; + private static final String OTEL_MEMBER_KEY = "ot="; private static final int EMPTY_SIZE = DATADOG_MEMBER_KEY.length(); // 3 private static final char MEMBER_SEPARATOR = ','; private static final char ELEMENT_SEPARATOR = ';'; @@ -86,7 +87,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { if (ddMemberIndex == -1) { // There was no dd member, so create an empty one with the _suffix_ - return empty(tagsFactory, value); + return empty(tagsFactory, value, extractOtelTraceState(value)); } List tagPairs = null; @@ -201,7 +202,8 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { ddMemberValueEnd, maxUnknownSize, lastParentId, - orgPropagationMarkerTagValue); + orgPropagationMarkerTagValue, + extractOtelTraceState(value)); } @Override @@ -226,6 +228,10 @@ protected int estimateHeaderSize(PTags pTags) { // We assume there is no Datadog list-member size += pTags.tracestate.length(); } + OtelTraceState otelTraceState = pTags.getOtelTraceState(); + if (otelTraceState != null) { + size += OTEL_MEMBER_KEY.length() + otelTraceState.length() + 1; + } return size; } @@ -290,9 +296,8 @@ protected int appendSuffix(StringBuilder sb, PTags ptags, int size) { sb.setLength(0); size = 0; } - // Append all other non-Datadog list-members - int newSize = cleanUpAndAppendSuffix(sb, ptags, size); - if (newSize != size) { + // Append the managed OTel member and all other non-Datadog list-members + if (appendOtelAndVendorMembers(sb, ptags, size != 0)) { // We don't care about the total size in bytes here, but only the fact that we added something // that should be returned size = Math.max(size, EMPTY_SIZE + 1); @@ -698,50 +703,104 @@ private static int cleanUpAndAppendUnknown(StringBuilder sb, W3CPTags w3CPTags, return size; } - private static int cleanUpAndAppendSuffix(StringBuilder sb, PTags ptags, int size) { + private static boolean appendOtelAndVendorMembers( + StringBuilder sb, PTags ptags, boolean hasDatadogMember) { String original = ptags.tracestate; - if (original == null) { - return size; - } - int ddMemberStart = (ptags instanceof W3CPTags) ? ((W3CPTags) ptags).ddMemberStart : -1; - int remainingMemberAllowed = size == 0 ? MAX_MEMBER_COUNT : MAX_MEMBER_COUNT - 1; - int len = original.length(); - int memberStart = findNextMember(original, 0); - while (memberStart < len) { + OtelTraceState otelTraceState = ptags.getOtelTraceState(); + int remainingMembers = MAX_MEMBER_COUNT - (hasDatadogMember ? 1 : 0); + int otherMemberPosition = 0; + boolean otelTraceStateAppended = false; + boolean memberAppended = false; + int len = original == null ? 0 : original.length(); + int memberStart = original == null ? 0 : findNextMember(original, 0); + while (memberStart < len && remainingMembers > 0) { // Look for member end position int memberEnd = original.indexOf(MEMBER_SEPARATOR, memberStart); if (memberEnd < 0) { memberEnd = len; } - // Try to define Datadog member start if not already found - if (ddMemberStart == -1) { - if (original.startsWith(DATADOG_MEMBER_KEY, memberStart)) { - ddMemberStart = memberStart; - } - } - // Skip Datadog member (already added with prefix and tags) - if (memberStart != ddMemberStart) { - if (sb.length() > 0) { - sb.append(MEMBER_SEPARATOR); - size++; + boolean managedMember = + original.startsWith(DATADOG_MEMBER_KEY, memberStart) + || original.startsWith(OTEL_MEMBER_KEY, memberStart); + if (!managedMember) { + if (otelTraceState != null + && !otelTraceStateAppended + && otelTraceState.getInheritedPosition() == otherMemberPosition) { + appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); + remainingMembers--; + otelTraceStateAppended = true; + memberAppended = true; + if (remainingMembers == 0) { + break; + } } int end = stripTrailingOWC(original, memberStart, memberEnd); - sb.append(original, memberStart, end); - size += (end - memberStart); - remainingMemberAllowed--; + appendMember(sb, original, memberStart, end); + remainingMembers--; + otherMemberPosition++; + memberAppended = true; } - // Check if remaining members are allowed - if (remainingMemberAllowed == 0) { - memberStart = len; - } else { - memberStart = findNextMember(original, memberEnd + 1); + memberStart = findNextMember(original, memberEnd + 1); + } + if (otelTraceState != null + && !otelTraceStateAppended + && remainingMembers > 0 + && otelTraceState.getInheritedPosition() == otherMemberPosition) { + appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); + memberAppended = true; + } + return memberAppended; + } + + private static void appendMember(StringBuilder sb, String member, int start, int end) { + if (sb.length() != 0) { + sb.append(MEMBER_SEPARATOR); + } + sb.append(member, start, end); + } + + private static void appendMember(StringBuilder sb, String key, String value) { + if (sb.length() != 0) { + sb.append(MEMBER_SEPARATOR); + } + sb.append(key).append(value); + } + + static OtelTraceState extractOtelTraceState(String tracestate) { + if (tracestate == null || tracestate.isEmpty()) { + return null; + } + int otherMemberPosition = 0; + int memberStart = findNextMember(tracestate, 0); + while (memberStart < tracestate.length()) { + int memberValueStart = validateMemberKey(tracestate, memberStart); + if (memberValueStart < 0) { + return null; } + int memberValueEnd = validateMemberValue(tracestate, memberValueStart); + if (memberValueEnd < 0) { + return null; + } + if (tracestate.startsWith(OTEL_MEMBER_KEY, memberStart)) { + int end = stripTrailingOWC(tracestate, memberValueStart, memberValueEnd); + return OtelTraceState.parse( + tracestate.substring(memberValueStart, end), otherMemberPosition); + } + if (!tracestate.startsWith(DATADOG_MEMBER_KEY, memberStart)) { + otherMemberPosition++; + } + memberStart = findNextMember(tracestate, memberValueEnd); } - return size; + return null; } static W3CPTags empty(PTagsFactory factory, String original) { - return empty(factory, original, 0, -1, -1); + return empty(factory, original, extractOtelTraceState(original)); + } + + private static W3CPTags empty( + PTagsFactory factory, String original, OtelTraceState otelTraceState) { + return empty(factory, original, 0, -1, -1, otelTraceState); } private static W3CPTags empty( @@ -750,6 +809,22 @@ private static W3CPTags empty( int firstMemberStart, int ddMemberStart, int ddMemberValueEnd) { + return empty( + factory, + original, + firstMemberStart, + ddMemberStart, + ddMemberValueEnd, + extractOtelTraceState(original)); + } + + private static W3CPTags empty( + PTagsFactory factory, + String original, + int firstMemberStart, + int ddMemberStart, + int ddMemberValueEnd, + OtelTraceState otelTraceState) { return new W3CPTags( factory, null, @@ -764,7 +839,8 @@ private static W3CPTags empty( ddMemberValueEnd, 0, null, - null); + null, + otelTraceState); } private static class W3CPTags extends PTags { @@ -799,7 +875,8 @@ public W3CPTags( int ddMemberValueEnd, int maxUnknownSize, CharSequence lastParentId, - TagValue orgPropagationMarkerTagValue) { + TagValue orgPropagationMarkerTagValue, + OtelTraceState otelTraceState) { super( factory, tagPairs, @@ -815,6 +892,7 @@ public W3CPTags( this.ddMemberStart = ddMemberStart; this.ddMemberValueEnd = ddMemberValueEnd; this.maxUnknownSize = maxUnknownSize; + setOtelTraceState(otelTraceState); } @Override From 510f79711e239683ebb2ebcfa529d0a0d1bd9b9c Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Fri, 4 Sep 2026 17:26:43 +0200 Subject: [PATCH 02/15] perf(propagation): avoid redundant tracestate parsing --- .../core/propagation/ptags/W3CPTagsCodec.java | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index d56a0076017..1ff92c4b6e0 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -50,13 +50,19 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int ddMemberValueEnd = -1; // dd member value end position including OWS (exclusive) int memberIndex = 0; int ddMemberIndex = -1; + int otelMemberValueStart = -1; + int otelMemberValueEnd = -1; + int otelMemberPosition = 0; + int otherMemberPosition = 0; while (memberStart < len) { if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? // TODO should we try to pick up the `dd` member anyway? return tagsFactory.empty(); } - if (ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart)) { + boolean datadogMember = value.startsWith(DATADOG_MEMBER_KEY, memberStart); + boolean otelMember = value.startsWith(OTEL_MEMBER_KEY, memberStart); + if (ddMemberIndex == -1 && datadogMember) { ddMemberStart = memberStart; ddMemberIndex = memberIndex; } @@ -69,6 +75,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { if (ddMemberValueStart == -1 && ddMemberIndex != -1) { ddMemberValueStart = pos; } + int memberValueStart = pos; pos = validateMemberValue(value, pos); if (pos < 0) { // TODO should we return one with an error? @@ -77,6 +84,15 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { if (ddMemberValueEnd == -1 && ddMemberIndex != -1) { ddMemberValueEnd = pos; } + if (otelMemberValueStart == -1) { + if (otelMember) { + otelMemberValueStart = memberValueStart; + otelMemberValueEnd = stripTrailingOWC(value, memberValueStart, pos); + otelMemberPosition = otherMemberPosition; + } else if (!datadogMember) { + otherMemberPosition++; + } + } memberStart = findNextMember(value, pos); if (memberStart < 0) { // TODO should we return one with an error? @@ -85,9 +101,15 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { memberIndex++; } + OtelTraceState otelTraceState = + otelMemberValueStart < 0 + ? null + : OtelTraceState.parse( + value.substring(otelMemberValueStart, otelMemberValueEnd), otelMemberPosition); + if (ddMemberIndex == -1) { // There was no dd member, so create an empty one with the _suffix_ - return empty(tagsFactory, value, extractOtelTraceState(value)); + return empty(tagsFactory, value, otelTraceState); } List tagPairs = null; @@ -159,7 +181,13 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { if (tagKey.equals(TRACE_ID_TAG)) { return tagsFactory.createInvalid(PROPAGATION_ERROR_MALFORMED_TID + tagValue); } - return empty(tagsFactory, value, firstMemberStart, ddMemberStart, ddMemberValueEnd); + return empty( + tagsFactory, + value, + firstMemberStart, + ddMemberStart, + ddMemberValueEnd, + otelTraceState); } if (tagKey.equals(DECISION_MAKER_TAG)) { decisionMakerTagValue = tagValue; @@ -203,7 +231,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { maxUnknownSize, lastParentId, orgPropagationMarkerTagValue, - extractOtelTraceState(value)); + otelTraceState); } @Override @@ -803,21 +831,6 @@ private static W3CPTags empty( return empty(factory, original, 0, -1, -1, otelTraceState); } - private static W3CPTags empty( - PTagsFactory factory, - String original, - int firstMemberStart, - int ddMemberStart, - int ddMemberValueEnd) { - return empty( - factory, - original, - firstMemberStart, - ddMemberStart, - ddMemberValueEnd, - extractOtelTraceState(original)); - } - private static W3CPTags empty( PTagsFactory factory, String original, From f585495b0493fdcea596e941ff38a7e4a0a46033 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 7 Sep 2026 11:42:49 +0200 Subject: [PATCH 03/15] fix(propagation): avoid double-counting OTel tracestate --- .../propagation/ptags/OtelTraceState.java | 13 +++++++--- .../core/propagation/ptags/W3CPTagsCodec.java | 26 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java index 3b8e837b5b9..c090fb5e067 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java @@ -3,17 +3,20 @@ final class OtelTraceState { private final String value; private final int inheritedPosition; + private final int originalMemberContributionSize; - private OtelTraceState(String value, int inheritedPosition) { + private OtelTraceState(String value, int inheritedPosition, int originalMemberContributionSize) { this.value = value; this.inheritedPosition = inheritedPosition; + this.originalMemberContributionSize = originalMemberContributionSize; } - static OtelTraceState parse(String raw, int inheritedPosition) { + static OtelTraceState parse( + String raw, int inheritedPosition, int originalMemberContributionSize) { if (raw == null || raw.isEmpty()) { return null; } - return new OtelTraceState(raw, inheritedPosition); + return new OtelTraceState(raw, inheritedPosition, originalMemberContributionSize); } String getValue() { @@ -27,4 +30,8 @@ int length() { int getInheritedPosition() { return inheritedPosition; } + + int getOriginalMemberContributionSize() { + return originalMemberContributionSize; + } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index 1ff92c4b6e0..df2d7d20ec3 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -50,8 +50,10 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int ddMemberValueEnd = -1; // dd member value end position including OWS (exclusive) int memberIndex = 0; int ddMemberIndex = -1; + int otelMemberStart = -1; int otelMemberValueStart = -1; int otelMemberValueEnd = -1; + int otelMemberEnd = -1; int otelMemberPosition = 0; int otherMemberPosition = 0; while (memberStart < len) { @@ -86,8 +88,10 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { } if (otelMemberValueStart == -1) { if (otelMember) { + otelMemberStart = memberStart; otelMemberValueStart = memberValueStart; otelMemberValueEnd = stripTrailingOWC(value, memberValueStart, pos); + otelMemberEnd = pos; otelMemberPosition = otherMemberPosition; } else if (!datadogMember) { otherMemberPosition++; @@ -105,7 +109,9 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { otelMemberValueStart < 0 ? null : OtelTraceState.parse( - value.substring(otelMemberValueStart, otelMemberValueEnd), otelMemberPosition); + value.substring(otelMemberValueStart, otelMemberValueEnd), + otelMemberPosition, + memberContributionSize(value, firstMemberStart, otelMemberStart, otelMemberEnd)); if (ddMemberIndex == -1) { // There was no dd member, so create an empty one with the _suffix_ @@ -245,19 +251,23 @@ protected int estimateHeaderSize(PTags pTags) { if (pTags.getSamplingPriority() != PrioritySampling.UNSET) { size += 5; // 's:-?[0-9]' + delimiter } + boolean includesOriginalTracestate = false; if (pTags instanceof W3CPTags) { W3CPTags w3CPTags = (W3CPTags) pTags; size += w3CPTags.maxUnknownSize; if (w3CPTags.ddMemberStart != -1) { size += (w3CPTags.tracestate.length() - (w3CPTags.ddMemberValueEnd - w3CPTags.ddMemberStart)); + includesOriginalTracestate = true; } } else if (pTags.tracestate != null) { // We assume there is no Datadog list-member size += pTags.tracestate.length(); + includesOriginalTracestate = true; } OtelTraceState otelTraceState = pTags.getOtelTraceState(); if (otelTraceState != null) { + size -= includesOriginalTracestate ? otelTraceState.getOriginalMemberContributionSize() : 0; size += OTEL_MEMBER_KEY.length() + otelTraceState.length() + 1; } return size; @@ -799,7 +809,8 @@ static OtelTraceState extractOtelTraceState(String tracestate) { return null; } int otherMemberPosition = 0; - int memberStart = findNextMember(tracestate, 0); + int firstMemberStart = findNextMember(tracestate, 0); + int memberStart = firstMemberStart; while (memberStart < tracestate.length()) { int memberValueStart = validateMemberKey(tracestate, memberStart); if (memberValueStart < 0) { @@ -812,7 +823,9 @@ static OtelTraceState extractOtelTraceState(String tracestate) { if (tracestate.startsWith(OTEL_MEMBER_KEY, memberStart)) { int end = stripTrailingOWC(tracestate, memberValueStart, memberValueEnd); return OtelTraceState.parse( - tracestate.substring(memberValueStart, end), otherMemberPosition); + tracestate.substring(memberValueStart, end), + otherMemberPosition, + memberContributionSize(tracestate, firstMemberStart, memberStart, memberValueEnd)); } if (!tracestate.startsWith(DATADOG_MEMBER_KEY, memberStart)) { otherMemberPosition++; @@ -822,6 +835,13 @@ static OtelTraceState extractOtelTraceState(String tracestate) { return null; } + private static int memberContributionSize( + String tracestate, int firstMemberStart, int memberStart, int memberEnd) { + int memberSize = memberEnd - memberStart; + boolean isOnlyMember = memberStart == firstMemberStart && memberEnd == tracestate.length(); + return isOnlyMember ? memberSize : memberSize + 1; + } + static W3CPTags empty(PTagsFactory factory, String original) { return empty(factory, original, extractOtelTraceState(original)); } From 43436f4df05179402dc993fcc2372c5e2f625759 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 7 Sep 2026 13:22:32 +0200 Subject: [PATCH 04/15] test(propagation): cover OTel tracestate parsing --- .../ptags/OtelTraceStateParsingTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java new file mode 100644 index 00000000000..853dd3be627 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java @@ -0,0 +1,31 @@ +package datadog.trace.core.propagation.ptags; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.Test; + +class OtelTraceStateParsingTest { + private static final String VALUE = "rv:0123456789abcd"; + private static final int INHERITED_POSITION = 2; + private static final int ORIGINAL_MEMBER_CONTRIBUTION_SIZE = 21; + + @Test + void ignoresAbsentValues() { + assertNull(OtelTraceState.parse(null, INHERITED_POSITION, ORIGINAL_MEMBER_CONTRIBUTION_SIZE)); + assertNull(OtelTraceState.parse("", INHERITED_POSITION, ORIGINAL_MEMBER_CONTRIBUTION_SIZE)); + } + + @Test + void retainsValueAndMemberMetadata() { + OtelTraceState state = + OtelTraceState.parse(VALUE, INHERITED_POSITION, ORIGINAL_MEMBER_CONTRIBUTION_SIZE); + + assertNotNull(state); + assertEquals(VALUE, state.getValue()); + assertEquals(VALUE.length(), state.length()); + assertEquals(INHERITED_POSITION, state.getInheritedPosition()); + assertEquals(ORIGINAL_MEMBER_CONTRIBUTION_SIZE, state.getOriginalMemberContributionSize()); + } +} From 77e740b90d31403ae26dd0b8e25fbc9bd84f55bc Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 7 Sep 2026 13:59:30 +0200 Subject: [PATCH 05/15] perf(propagation): reuse parsed otel tracestate --- .../trace/core/propagation/HttpCodec.java | 3 +- .../core/propagation/PropagationTags.java | 5 +++ .../core/propagation/ptags/PTagsFactory.java | 16 ++++++++- .../core/propagation/ptags/W3CPTagsCodec.java | 35 +++++++++++-------- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java index 38783860e9b..61e9a8f105e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java @@ -286,8 +286,7 @@ private void applyTraceContextToFirstContext( ExtractedContext traceContext, ExtractionCache extractionCache) { // Propagate newly extracted W3C tracestate to first valid context - String extractedTracestate = traceContext.getPropagationTags().getW3CTracestate(); - firstContext.getPropagationTags().updateW3CTracestate(extractedTracestate); + firstContext.getPropagationTags().updateW3CTracestateFrom(traceContext.getPropagationTags()); // Check if parent spans differ to reconcile them if (firstContext.getSpanId() != traceContext.getSpanId()) { // Override parent span id with W3C one diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java index 3a0c57a4dd8..0ebe630c87a 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java @@ -95,6 +95,11 @@ public interface Factory { */ public abstract void updateW3CTracestate(String tracestate); + /** Updates the original W3C tracestate header from {@code source}. */ + public void updateW3CTracestateFrom(PropagationTags source) { + updateW3CTracestate(source.getW3CTracestate()); + } + /** * Constructs a header value that includes valid propagated _dd.p.* tags and possibly a new * sampling decision tag _dd.p.dm based on the current state. Returns null if the value length diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java index 1661398c819..84f3c700269 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java @@ -542,9 +542,23 @@ public String getW3CTracestate() { @Override public void updateW3CTracestate(String tracestate) { + setW3CTracestate(tracestate, W3CPTagsCodec.extractOtelTraceState(tracestate)); + } + + @Override + public void updateW3CTracestateFrom(PropagationTags source) { + if (!(source instanceof PTags)) { + super.updateW3CTracestateFrom(source); + return; + } + PTags sourcePTags = (PTags) source; + setW3CTracestate(sourcePTags.tracestate, sourcePTags.getOtelTraceState()); + } + + private void setW3CTracestate(String tracestate, OtelTraceState otelTraceState) { clearCachedHeader(W3C); this.tracestate = tracestate; - setOtelTraceState(W3CPTagsCodec.extractOtelTraceState(tracestate)); + this.otelTraceState = otelTraceState; } OtelTraceState getOtelTraceState() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index df2d7d20ec3..34074be90d1 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -57,19 +57,19 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int otelMemberPosition = 0; int otherMemberPosition = 0; while (memberStart < len) { + int currentMemberStart = memberStart; if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? // TODO should we try to pick up the `dd` member anyway? return tagsFactory.empty(); } - boolean datadogMember = value.startsWith(DATADOG_MEMBER_KEY, memberStart); - boolean otelMember = value.startsWith(OTEL_MEMBER_KEY, memberStart); + boolean datadogMember = value.startsWith(DATADOG_MEMBER_KEY, currentMemberStart); if (ddMemberIndex == -1 && datadogMember) { - ddMemberStart = memberStart; + ddMemberStart = currentMemberStart; ddMemberIndex = memberIndex; } // Validate the member key - int pos = validateMemberKey(value, memberStart); + int pos = validateMemberKey(value, currentMemberStart); if (pos < 0) { // TODO should we return one with an error? return tagsFactory.empty(); @@ -86,23 +86,28 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { if (ddMemberValueEnd == -1 && ddMemberIndex != -1) { ddMemberValueEnd = pos; } - if (otelMemberValueStart == -1) { - if (otelMember) { - otelMemberStart = memberStart; - otelMemberValueStart = memberValueStart; - otelMemberValueEnd = stripTrailingOWC(value, memberValueStart, pos); - otelMemberEnd = pos; - otelMemberPosition = otherMemberPosition; - } else if (!datadogMember) { - otherMemberPosition++; - } - } memberStart = findNextMember(value, pos); if (memberStart < 0) { // TODO should we return one with an error? return tagsFactory.empty(); } memberIndex++; + if (otelMemberValueStart != -1) { + continue; + } + if (datadogMember) { + continue; + } + boolean otelMember = value.startsWith(OTEL_MEMBER_KEY, currentMemberStart); + if (otelMember) { + otelMemberStart = currentMemberStart; + otelMemberValueStart = memberValueStart; + otelMemberValueEnd = stripTrailingOWC(value, memberValueStart, pos); + otelMemberEnd = pos; + otelMemberPosition = otherMemberPosition; + } else { + otherMemberPosition++; + } } OtelTraceState otelTraceState = From df6fb82c1ce6e217d2a148933fab7fb880eac4b5 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Fri, 11 Sep 2026 14:12:14 +0200 Subject: [PATCH 06/15] refactor(otel): rename class members for clarity --- .../core/propagation/ptags/OtelTraceState.java | 16 ++++++++-------- .../core/propagation/ptags/W3CPTagsCodec.java | 6 +++--- .../ptags/OtelTraceStateParsingTest.java | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java index c090fb5e067..d6e64a7a32c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java @@ -2,13 +2,13 @@ final class OtelTraceState { private final String value; - private final int inheritedPosition; - private final int originalMemberContributionSize; + private final int originalPosition; + private final int originalSize; private OtelTraceState(String value, int inheritedPosition, int originalMemberContributionSize) { this.value = value; - this.inheritedPosition = inheritedPosition; - this.originalMemberContributionSize = originalMemberContributionSize; + this.originalPosition = inheritedPosition; + this.originalSize = originalMemberContributionSize; } static OtelTraceState parse( @@ -27,11 +27,11 @@ int length() { return value.length(); } - int getInheritedPosition() { - return inheritedPosition; + int getOriginalPosition() { + return originalPosition; } - int getOriginalMemberContributionSize() { - return originalMemberContributionSize; + int getOriginalSize() { + return originalSize; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index 34074be90d1..9a87694e93c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -272,7 +272,7 @@ protected int estimateHeaderSize(PTags pTags) { } OtelTraceState otelTraceState = pTags.getOtelTraceState(); if (otelTraceState != null) { - size -= includesOriginalTracestate ? otelTraceState.getOriginalMemberContributionSize() : 0; + size -= includesOriginalTracestate ? otelTraceState.getOriginalSize() : 0; size += OTEL_MEMBER_KEY.length() + otelTraceState.length() + 1; } return size; @@ -768,7 +768,7 @@ private static boolean appendOtelAndVendorMembers( if (!managedMember) { if (otelTraceState != null && !otelTraceStateAppended - && otelTraceState.getInheritedPosition() == otherMemberPosition) { + && otelTraceState.getOriginalPosition() == otherMemberPosition) { appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); remainingMembers--; otelTraceStateAppended = true; @@ -788,7 +788,7 @@ private static boolean appendOtelAndVendorMembers( if (otelTraceState != null && !otelTraceStateAppended && remainingMembers > 0 - && otelTraceState.getInheritedPosition() == otherMemberPosition) { + && otelTraceState.getOriginalPosition() == otherMemberPosition) { appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); memberAppended = true; } diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java index 853dd3be627..8e39285a56d 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java @@ -25,7 +25,7 @@ void retainsValueAndMemberMetadata() { assertNotNull(state); assertEquals(VALUE, state.getValue()); assertEquals(VALUE.length(), state.length()); - assertEquals(INHERITED_POSITION, state.getInheritedPosition()); - assertEquals(ORIGINAL_MEMBER_CONTRIBUTION_SIZE, state.getOriginalMemberContributionSize()); + assertEquals(INHERITED_POSITION, state.getOriginalPosition()); + assertEquals(ORIGINAL_MEMBER_CONTRIBUTION_SIZE, state.getOriginalSize()); } } From a647e3d675314bbac821d7f27610e8f0df8f997e Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Fri, 11 Sep 2026 15:04:53 +0200 Subject: [PATCH 07/15] tracestate parsing refactor --- .../core/propagation/ptags/W3CPTagsCodec.java | 76 +++++++------------ 1 file changed, 29 insertions(+), 47 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index 9a87694e93c..86eea13b9ef 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -50,74 +50,56 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int ddMemberValueEnd = -1; // dd member value end position including OWS (exclusive) int memberIndex = 0; int ddMemberIndex = -1; - int otelMemberStart = -1; - int otelMemberValueStart = -1; - int otelMemberValueEnd = -1; - int otelMemberEnd = -1; - int otelMemberPosition = 0; + OtelTraceState otelTraceState = null; int otherMemberPosition = 0; while (memberStart < len) { - int currentMemberStart = memberStart; if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? // TODO should we try to pick up the `dd` member anyway? return tagsFactory.empty(); } - boolean datadogMember = value.startsWith(DATADOG_MEMBER_KEY, currentMemberStart); - if (ddMemberIndex == -1 && datadogMember) { - ddMemberStart = currentMemberStart; - ddMemberIndex = memberIndex; - } // Validate the member key - int pos = validateMemberKey(value, currentMemberStart); - if (pos < 0) { + int memberValueStart = validateMemberKey(value, memberStart); + if (memberValueStart < 0) { // TODO should we return one with an error? return tagsFactory.empty(); } - if (ddMemberValueStart == -1 && ddMemberIndex != -1) { - ddMemberValueStart = pos; - } - int memberValueStart = pos; - pos = validateMemberValue(value, pos); - if (pos < 0) { + int memberValueEnd = validateMemberValue(value, memberValueStart); + if (memberValueEnd < 0) { // TODO should we return one with an error? return tagsFactory.empty(); } - if (ddMemberValueEnd == -1 && ddMemberIndex != -1) { - ddMemberValueEnd = pos; + + boolean datadogMember = + ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart); + boolean otelMember = + !datadogMember + && otelTraceState == null + && value.startsWith(OTEL_MEMBER_KEY, memberStart); + if (datadogMember) { + ddMemberStart = memberStart; + ddMemberValueStart = memberValueStart; + ddMemberIndex = memberIndex; + ddMemberValueEnd = memberValueEnd; + } else if (otelMember) { + otelTraceState = + OtelTraceState.parse( + value.substring( + memberValueStart, stripTrailingOWC(value, memberValueStart, memberValueEnd)), + otherMemberPosition, + memberContributionSize(value, firstMemberStart, memberStart, memberValueEnd)); + } else { + otherMemberPosition++; } - memberStart = findNextMember(value, pos); + + memberIndex++; + memberStart = findNextMember(value, memberValueEnd); if (memberStart < 0) { // TODO should we return one with an error? return tagsFactory.empty(); } - memberIndex++; - if (otelMemberValueStart != -1) { - continue; - } - if (datadogMember) { - continue; - } - boolean otelMember = value.startsWith(OTEL_MEMBER_KEY, currentMemberStart); - if (otelMember) { - otelMemberStart = currentMemberStart; - otelMemberValueStart = memberValueStart; - otelMemberValueEnd = stripTrailingOWC(value, memberValueStart, pos); - otelMemberEnd = pos; - otelMemberPosition = otherMemberPosition; - } else { - otherMemberPosition++; - } } - OtelTraceState otelTraceState = - otelMemberValueStart < 0 - ? null - : OtelTraceState.parse( - value.substring(otelMemberValueStart, otelMemberValueEnd), - otelMemberPosition, - memberContributionSize(value, firstMemberStart, otelMemberStart, otelMemberEnd)); - if (ddMemberIndex == -1) { // There was no dd member, so create an empty one with the _suffix_ return empty(tagsFactory, value, otelTraceState); From ed53706240fd413e5413a116db6aedcb6720197c Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Fri, 11 Sep 2026 15:52:34 +0200 Subject: [PATCH 08/15] nit: better argument names in class methods --- .../trace/core/propagation/ptags/OtelTraceState.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java index d6e64a7a32c..ad931281c82 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java @@ -5,18 +5,18 @@ final class OtelTraceState { private final int originalPosition; private final int originalSize; - private OtelTraceState(String value, int inheritedPosition, int originalMemberContributionSize) { + private OtelTraceState(String value, int originalPosition, int originalSize) { this.value = value; - this.originalPosition = inheritedPosition; - this.originalSize = originalMemberContributionSize; + this.originalPosition = originalPosition; + this.originalSize = originalSize; } static OtelTraceState parse( - String raw, int inheritedPosition, int originalMemberContributionSize) { + String raw, int originalPosition, int originalSize) { if (raw == null || raw.isEmpty()) { return null; } - return new OtelTraceState(raw, inheritedPosition, originalMemberContributionSize); + return new OtelTraceState(raw, originalPosition, originalSize); } String getValue() { From 074cb0d48f2c5f3f1623339af73a7d71f24a1447 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Fri, 11 Sep 2026 16:04:41 +0200 Subject: [PATCH 09/15] infer ot= member position from already existing memberIndex --- .../trace/core/propagation/ptags/OtelTraceState.java | 3 +-- .../trace/core/propagation/ptags/W3CPTagsCodec.java | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java index ad931281c82..3ab962360e8 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java @@ -11,8 +11,7 @@ private OtelTraceState(String value, int originalPosition, int originalSize) { this.originalSize = originalSize; } - static OtelTraceState parse( - String raw, int originalPosition, int originalSize) { + static OtelTraceState parse(String raw, int originalPosition, int originalSize) { if (raw == null || raw.isEmpty()) { return null; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index 86eea13b9ef..0e84820054f 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -50,8 +50,6 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int ddMemberValueEnd = -1; // dd member value end position including OWS (exclusive) int memberIndex = 0; int ddMemberIndex = -1; - OtelTraceState otelTraceState = null; - int otherMemberPosition = 0; while (memberStart < len) { if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? @@ -82,14 +80,15 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { ddMemberIndex = memberIndex; ddMemberValueEnd = memberValueEnd; } else if (otelMember) { + // Position to retain for this member (if left unchanged) + // Indexing after an eventual dd= member which will be placed first + int memberPosition = memberIndex - (ddMemberStart >= 0 ? 1 : 0); otelTraceState = OtelTraceState.parse( value.substring( memberValueStart, stripTrailingOWC(value, memberValueStart, memberValueEnd)), - otherMemberPosition, + memberPosition, memberContributionSize(value, firstMemberStart, memberStart, memberValueEnd)); - } else { - otherMemberPosition++; } memberIndex++; From c7a64c30fc9926604c089194dd20eb09fd5c1d45 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Fri, 11 Sep 2026 16:13:18 +0200 Subject: [PATCH 10/15] Update W3CPTagsCodec.java --- .../java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index 0e84820054f..efefbcfb9e9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -50,6 +50,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int ddMemberValueEnd = -1; // dd member value end position including OWS (exclusive) int memberIndex = 0; int ddMemberIndex = -1; + OtelTraceState otelTraceState = null; while (memberStart < len) { if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? From 9fdbda376e0b0ba43ce6171d33ac2652ef480e7d Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 14 Sep 2026 11:03:26 +0200 Subject: [PATCH 11/15] fix(propagation): preserve otel state after duplicate datadog members --- .../core/propagation/ptags/W3CPTagsCodec.java | 23 +++++++++++-------- .../propagation/W3CPropagationTagsTest.java | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index efefbcfb9e9..f211d5d6d70 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -51,6 +51,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int memberIndex = 0; int ddMemberIndex = -1; OtelTraceState otelTraceState = null; + int otherMemberPosition = 0; while (memberStart < len) { if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? @@ -69,27 +70,31 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { return tagsFactory.empty(); } + // Keep detecting duplicate dd members until ot is found because injection drops them, + // so they must not advance its saved position. boolean datadogMember = - ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart); + (ddMemberIndex == -1 || otelTraceState == null) + && value.startsWith(DATADOG_MEMBER_KEY, memberStart); boolean otelMember = !datadogMember && otelTraceState == null && value.startsWith(OTEL_MEMBER_KEY, memberStart); if (datadogMember) { - ddMemberStart = memberStart; - ddMemberValueStart = memberValueStart; - ddMemberIndex = memberIndex; - ddMemberValueEnd = memberValueEnd; + if (ddMemberIndex == -1) { + ddMemberStart = memberStart; + ddMemberValueStart = memberValueStart; + ddMemberIndex = memberIndex; + ddMemberValueEnd = memberValueEnd; + } } else if (otelMember) { - // Position to retain for this member (if left unchanged) - // Indexing after an eventual dd= member which will be placed first - int memberPosition = memberIndex - (ddMemberStart >= 0 ? 1 : 0); otelTraceState = OtelTraceState.parse( value.substring( memberValueStart, stripTrailingOWC(value, memberValueStart, memberValueEnd)), - memberPosition, + otherMemberPosition, memberContributionSize(value, firstMemberStart, memberStart, memberValueEnd)); + } else if (otelTraceState == null) { + otherMemberPosition++; } memberIndex++; diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java index 491b8fb32ab..ca219aa229d 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java @@ -241,6 +241,7 @@ static IntStream memberCountArguments() { "dd before other | 'dd=s:0;t.dm:934086a687-3,other=whatever' | 'dd=s:0;t.dm:934086a687-3,other=whatever' | [_dd.p.dm: 934086a687-3] ", "some before dd before other | 'some=thing,dd=s:0;t.dm:934086a687-3,other=whatever' | 'dd=s:0;t.dm:934086a687-3,some=thing,other=whatever' | [_dd.p.dm: 934086a687-3] ", "no dd | 'some=thing,other=whatever' | 'some=thing,other=whatever' | [:] ", + "duplicate dd before ot | 'dd=s:1,dd=s:0,ot=rv:ef284ace7a91e1' | 'dd=s:1,ot=rv:ef284ace7a91e1' | [:] ", "dd with origin and dm | 'dd=s:0;o:some;t.dm:934086a686-4' | 'dd=s:0;o:some;t.dm:934086a686-4' | [_dd.p.dm: 934086a686-4] ", "dd with unknown key | 'dd=s:0;x:unknown;t.dm:934086a686-4' | 'dd=s:0;t.dm:934086a686-4;x:unknown' | [_dd.p.dm: 934086a686-4] ", "other before dd with unknown | 'other=whatever,dd=s:0;x:unknown;t.dm:934086a686-4' | 'dd=s:0;t.dm:934086a686-4;x:unknown,other=whatever' | [_dd.p.dm: 934086a686-4] ", From d6258b9de667fefa9c4921ae72b386f672f1bc3d Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 14 Sep 2026 11:24:48 +0200 Subject: [PATCH 12/15] perf(propagation): avoid copying otel tracestate values --- .../propagation/ptags/OtelTraceState.java | 10 ++-- .../core/propagation/ptags/W3CPTagsCodec.java | 57 ++++++++++++------- .../ptags/OtelTraceStateParsingTest.java | 7 ++- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java index 3ab962360e8..dc6456e88ec 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java @@ -1,24 +1,24 @@ package datadog.trace.core.propagation.ptags; final class OtelTraceState { - private final String value; + private final CharSequence value; private final int originalPosition; private final int originalSize; - private OtelTraceState(String value, int originalPosition, int originalSize) { + private OtelTraceState(CharSequence value, int originalPosition, int originalSize) { this.value = value; this.originalPosition = originalPosition; this.originalSize = originalSize; } - static OtelTraceState parse(String raw, int originalPosition, int originalSize) { - if (raw == null || raw.isEmpty()) { + static OtelTraceState parse(CharSequence raw, int originalPosition, int originalSize) { + if (raw == null || raw.length() == 0) { return null; } return new OtelTraceState(raw, originalPosition, originalSize); } - String getValue() { + CharSequence getValue() { return value; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index f211d5d6d70..a9a711fac38 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -9,6 +9,7 @@ import datadog.trace.core.propagation.PropagationTags; import datadog.trace.core.propagation.ptags.PTagsFactory.PTags; import datadog.trace.core.propagation.ptags.TagElement.Encoding; +import datadog.trace.util.SubSequence; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -50,7 +51,10 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int ddMemberValueEnd = -1; // dd member value end position including OWS (exclusive) int memberIndex = 0; int ddMemberIndex = -1; - OtelTraceState otelTraceState = null; + int otelMemberStart = -1; + int otelMemberValueStart = -1; + int otelMemberValueEnd = -1; + int otelMemberPosition = -1; int otherMemberPosition = 0; while (memberStart < len) { if (memberIndex == MAX_MEMBER_COUNT) { @@ -73,12 +77,10 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { // Keep detecting duplicate dd members until ot is found because injection drops them, // so they must not advance its saved position. boolean datadogMember = - (ddMemberIndex == -1 || otelTraceState == null) + (ddMemberIndex == -1 || otelMemberStart == -1) && value.startsWith(DATADOG_MEMBER_KEY, memberStart); boolean otelMember = - !datadogMember - && otelTraceState == null - && value.startsWith(OTEL_MEMBER_KEY, memberStart); + !datadogMember && otelMemberStart == -1 && value.startsWith(OTEL_MEMBER_KEY, memberStart); if (datadogMember) { if (ddMemberIndex == -1) { ddMemberStart = memberStart; @@ -87,13 +89,11 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { ddMemberValueEnd = memberValueEnd; } } else if (otelMember) { - otelTraceState = - OtelTraceState.parse( - value.substring( - memberValueStart, stripTrailingOWC(value, memberValueStart, memberValueEnd)), - otherMemberPosition, - memberContributionSize(value, firstMemberStart, memberStart, memberValueEnd)); - } else if (otelTraceState == null) { + otelMemberStart = memberStart; + otelMemberValueStart = memberValueStart; + otelMemberValueEnd = memberValueEnd; + otelMemberPosition = otherMemberPosition; + } else if (otelMemberStart == -1) { otherMemberPosition++; } @@ -105,6 +105,16 @@ memberValueStart, stripTrailingOWC(value, memberValueStart, memberValueEnd)), } } + OtelTraceState otelTraceState = null; + if (otelMemberStart != -1) { + int valueEnd = stripTrailingOWC(value, otelMemberValueStart, otelMemberValueEnd); + otelTraceState = + OtelTraceState.parse( + SubSequence.of(value, otelMemberValueStart, valueEnd), + otelMemberPosition, + memberContributionSize(value, firstMemberStart, otelMemberStart, otelMemberValueEnd)); + } + if (ddMemberIndex == -1) { // There was no dd member, so create an empty one with the _suffix_ return empty(tagsFactory, value, otelTraceState); @@ -789,7 +799,7 @@ private static void appendMember(StringBuilder sb, String member, int start, int sb.append(member, start, end); } - private static void appendMember(StringBuilder sb, String key, String value) { + private static void appendMember(StringBuilder sb, String key, CharSequence value) { if (sb.length() != 0) { sb.append(MEMBER_SEPARATOR); } @@ -803,6 +813,9 @@ static OtelTraceState extractOtelTraceState(String tracestate) { int otherMemberPosition = 0; int firstMemberStart = findNextMember(tracestate, 0); int memberStart = firstMemberStart; + int otelMemberStart = -1; + int otelMemberValueStart = -1; + int otelMemberValueEnd = -1; while (memberStart < tracestate.length()) { int memberValueStart = validateMemberKey(tracestate, memberStart); if (memberValueStart < 0) { @@ -813,18 +826,24 @@ static OtelTraceState extractOtelTraceState(String tracestate) { return null; } if (tracestate.startsWith(OTEL_MEMBER_KEY, memberStart)) { - int end = stripTrailingOWC(tracestate, memberValueStart, memberValueEnd); - return OtelTraceState.parse( - tracestate.substring(memberValueStart, end), - otherMemberPosition, - memberContributionSize(tracestate, firstMemberStart, memberStart, memberValueEnd)); + otelMemberStart = memberStart; + otelMemberValueStart = memberValueStart; + otelMemberValueEnd = memberValueEnd; + break; } if (!tracestate.startsWith(DATADOG_MEMBER_KEY, memberStart)) { otherMemberPosition++; } memberStart = findNextMember(tracestate, memberValueEnd); } - return null; + if (otelMemberStart == -1) { + return null; + } + int valueEnd = stripTrailingOWC(tracestate, otelMemberValueStart, otelMemberValueEnd); + return OtelTraceState.parse( + SubSequence.of(tracestate, otelMemberValueStart, valueEnd), + otherMemberPosition, + memberContributionSize(tracestate, firstMemberStart, otelMemberStart, otelMemberValueEnd)); } private static int memberContributionSize( diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java index 8e39285a56d..5781864b5c6 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java @@ -3,7 +3,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import datadog.trace.util.SubSequence; import org.junit.jupiter.api.Test; class OtelTraceStateParsingTest { @@ -19,11 +21,12 @@ void ignoresAbsentValues() { @Test void retainsValueAndMemberMetadata() { + SubSequence value = SubSequence.of(VALUE, 0, VALUE.length()); OtelTraceState state = - OtelTraceState.parse(VALUE, INHERITED_POSITION, ORIGINAL_MEMBER_CONTRIBUTION_SIZE); + OtelTraceState.parse(value, INHERITED_POSITION, ORIGINAL_MEMBER_CONTRIBUTION_SIZE); assertNotNull(state); - assertEquals(VALUE, state.getValue()); + assertSame(value, state.getValue()); assertEquals(VALUE.length(), state.length()); assertEquals(INHERITED_POSITION, state.getOriginalPosition()); assertEquals(ORIGINAL_MEMBER_CONTRIBUTION_SIZE, state.getOriginalSize()); From d4bb2e142fd3f009efd978d75122b8fc090130de Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 14 Sep 2026 11:58:46 +0200 Subject: [PATCH 13/15] refactor(otel): store absolute position for otel member, compute offset when generating --- .../core/propagation/ptags/W3CPTagsCodec.java | 36 +++++++++---------- .../propagation/W3CPropagationTagsTest.java | 2 ++ .../ptags/OtelTraceStateParsingTest.java | 9 +++++ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index a9a711fac38..753832d3e59 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -55,7 +55,6 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { int otelMemberValueStart = -1; int otelMemberValueEnd = -1; int otelMemberPosition = -1; - int otherMemberPosition = 0; while (memberStart < len) { if (memberIndex == MAX_MEMBER_COUNT) { // TODO should we return one with an error? @@ -74,11 +73,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { return tagsFactory.empty(); } - // Keep detecting duplicate dd members until ot is found because injection drops them, - // so they must not advance its saved position. - boolean datadogMember = - (ddMemberIndex == -1 || otelMemberStart == -1) - && value.startsWith(DATADOG_MEMBER_KEY, memberStart); + boolean datadogMember = value.startsWith(DATADOG_MEMBER_KEY, memberStart); boolean otelMember = !datadogMember && otelMemberStart == -1 && value.startsWith(OTEL_MEMBER_KEY, memberStart); if (datadogMember) { @@ -92,9 +87,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { otelMemberStart = memberStart; otelMemberValueStart = memberValueStart; otelMemberValueEnd = memberValueEnd; - otelMemberPosition = otherMemberPosition; - } else if (otelMemberStart == -1) { - otherMemberPosition++; + otelMemberPosition = memberIndex; } memberIndex++; @@ -749,6 +742,10 @@ private static boolean appendOtelAndVendorMembers( OtelTraceState otelTraceState = ptags.getOtelTraceState(); int remainingMembers = MAX_MEMBER_COUNT - (hasDatadogMember ? 1 : 0); int otherMemberPosition = 0; + int originalMemberPosition = 0; + int otelMemberPositionOffset = 0; + int otelMemberOriginalPosition = + otelTraceState == null ? -1 : otelTraceState.getOriginalPosition(); boolean otelTraceStateAppended = false; boolean memberAppended = false; int len = original == null ? 0 : original.length(); @@ -759,13 +756,15 @@ private static boolean appendOtelAndVendorMembers( if (memberEnd < 0) { memberEnd = len; } - boolean managedMember = - original.startsWith(DATADOG_MEMBER_KEY, memberStart) - || original.startsWith(OTEL_MEMBER_KEY, memberStart); + boolean datadogMember = original.startsWith(DATADOG_MEMBER_KEY, memberStart); + boolean managedMember = datadogMember || original.startsWith(OTEL_MEMBER_KEY, memberStart); + if (datadogMember && originalMemberPosition < otelMemberOriginalPosition) { + otelMemberPositionOffset++; + } if (!managedMember) { if (otelTraceState != null && !otelTraceStateAppended - && otelTraceState.getOriginalPosition() == otherMemberPosition) { + && otelMemberOriginalPosition - otelMemberPositionOffset == otherMemberPosition) { appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); remainingMembers--; otelTraceStateAppended = true; @@ -780,12 +779,13 @@ private static boolean appendOtelAndVendorMembers( otherMemberPosition++; memberAppended = true; } + originalMemberPosition++; memberStart = findNextMember(original, memberEnd + 1); } if (otelTraceState != null && !otelTraceStateAppended && remainingMembers > 0 - && otelTraceState.getOriginalPosition() == otherMemberPosition) { + && otelMemberOriginalPosition - otelMemberPositionOffset == otherMemberPosition) { appendMember(sb, OTEL_MEMBER_KEY, otelTraceState.getValue()); memberAppended = true; } @@ -810,7 +810,7 @@ static OtelTraceState extractOtelTraceState(String tracestate) { if (tracestate == null || tracestate.isEmpty()) { return null; } - int otherMemberPosition = 0; + int memberPosition = 0; int firstMemberStart = findNextMember(tracestate, 0); int memberStart = firstMemberStart; int otelMemberStart = -1; @@ -831,9 +831,7 @@ static OtelTraceState extractOtelTraceState(String tracestate) { otelMemberValueEnd = memberValueEnd; break; } - if (!tracestate.startsWith(DATADOG_MEMBER_KEY, memberStart)) { - otherMemberPosition++; - } + memberPosition++; memberStart = findNextMember(tracestate, memberValueEnd); } if (otelMemberStart == -1) { @@ -842,7 +840,7 @@ static OtelTraceState extractOtelTraceState(String tracestate) { int valueEnd = stripTrailingOWC(tracestate, otelMemberValueStart, otelMemberValueEnd); return OtelTraceState.parse( SubSequence.of(tracestate, otelMemberValueStart, valueEnd), - otherMemberPosition, + memberPosition, memberContributionSize(tracestate, firstMemberStart, otelMemberStart, otelMemberValueEnd)); } diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java index ca219aa229d..6f920526138 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/W3CPropagationTagsTest.java @@ -242,6 +242,8 @@ static IntStream memberCountArguments() { "some before dd before other | 'some=thing,dd=s:0;t.dm:934086a687-3,other=whatever' | 'dd=s:0;t.dm:934086a687-3,some=thing,other=whatever' | [_dd.p.dm: 934086a687-3] ", "no dd | 'some=thing,other=whatever' | 'some=thing,other=whatever' | [:] ", "duplicate dd before ot | 'dd=s:1,dd=s:0,ot=rv:ef284ace7a91e1' | 'dd=s:1,ot=rv:ef284ace7a91e1' | [:] ", + "dd offset before ot | 'first=value,dd=s:1,sec=value,dd=s:0,ot=rv:ef284ace7a91e1' | 'dd=s:1,first=value,sec=value,ot=rv:ef284ace7a91e1' | [:] ", + "dd after ot does not offset | 'first=value,ot=rv:ef284ace7a91e1,dd=s:1,sec=value' | 'dd=s:1,first=value,ot=rv:ef284ace7a91e1,sec=value' | [:] ", "dd with origin and dm | 'dd=s:0;o:some;t.dm:934086a686-4' | 'dd=s:0;o:some;t.dm:934086a686-4' | [_dd.p.dm: 934086a686-4] ", "dd with unknown key | 'dd=s:0;x:unknown;t.dm:934086a686-4' | 'dd=s:0;t.dm:934086a686-4;x:unknown' | [_dd.p.dm: 934086a686-4] ", "other before dd with unknown | 'other=whatever,dd=s:0;x:unknown;t.dm:934086a686-4' | 'dd=s:0;t.dm:934086a686-4;x:unknown,other=whatever' | [_dd.p.dm: 934086a686-4] ", diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java index 5781864b5c6..97a6accae40 100644 --- a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/OtelTraceStateParsingTest.java @@ -31,4 +31,13 @@ void retainsValueAndMemberMetadata() { assertEquals(INHERITED_POSITION, state.getOriginalPosition()); assertEquals(ORIGINAL_MEMBER_CONTRIBUTION_SIZE, state.getOriginalSize()); } + + @Test + void extractsOriginalMemberPosition() { + OtelTraceState state = + W3CPTagsCodec.extractOtelTraceState("first=value,dd=s:1,dd=s:0,ot=" + VALUE); + + assertNotNull(state); + assertEquals(3, state.getOriginalPosition()); + } } From a6910415a997d95c783896491c348343d031be46 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 14 Sep 2026 12:18:38 +0200 Subject: [PATCH 14/15] comment `otelMemberPositionOffset` increment + stop checking dd= if already found in extraction --- .../core/propagation/ptags/W3CPTagsCodec.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index 753832d3e59..a5799d8f0d6 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -73,16 +73,15 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { return tagsFactory.empty(); } - boolean datadogMember = value.startsWith(DATADOG_MEMBER_KEY, memberStart); + boolean datadogMember = ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart); boolean otelMember = !datadogMember && otelMemberStart == -1 && value.startsWith(OTEL_MEMBER_KEY, memberStart); if (datadogMember) { - if (ddMemberIndex == -1) { - ddMemberStart = memberStart; - ddMemberValueStart = memberValueStart; - ddMemberIndex = memberIndex; - ddMemberValueEnd = memberValueEnd; - } + ddMemberStart = memberStart; + ddMemberValueStart = memberValueStart; + ddMemberIndex = memberIndex; + ddMemberValueEnd = memberValueEnd; + } else if (otelMember) { otelMemberStart = memberStart; otelMemberValueStart = memberValueStart; @@ -758,6 +757,7 @@ private static boolean appendOtelAndVendorMembers( } boolean datadogMember = original.startsWith(DATADOG_MEMBER_KEY, memberStart); boolean managedMember = datadogMember || original.startsWith(OTEL_MEMBER_KEY, memberStart); + // offset to correct for dd members that were dropped/relocated before ot's original position if (datadogMember && originalMemberPosition < otelMemberOriginalPosition) { otelMemberPositionOffset++; } From 900f8386b5195c8c5dbb7710ba015b2c8d387b22 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 14 Sep 2026 13:23:23 +0200 Subject: [PATCH 15/15] spotless apply --- .../datadog/trace/core/propagation/ptags/W3CPTagsCodec.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java index a5799d8f0d6..9cccf3fd45c 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/W3CPTagsCodec.java @@ -73,7 +73,8 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) { return tagsFactory.empty(); } - boolean datadogMember = ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart); + boolean datadogMember = + ddMemberIndex == -1 && value.startsWith(DATADOG_MEMBER_KEY, memberStart); boolean otelMember = !datadogMember && otelMemberStart == -1 && value.startsWith(OTEL_MEMBER_KEY, memberStart); if (datadogMember) {