From 25dcc81f3153b83daea626e9bd547abac9c3d2da Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 27 Aug 2026 10:19:59 +0200 Subject: [PATCH 1/5] 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 397a1ff69a711b8d43542f7d87331e7f664bc2c5 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Fri, 4 Sep 2026 17:26:43 +0200 Subject: [PATCH 2/5] 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 7a882832e476cad5e01eeed1508813d0df521e13 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 7 Sep 2026 11:42:49 +0200 Subject: [PATCH 3/5] 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 712e413e7b3eeb095d1edc88c212421cb0488998 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 7 Sep 2026 13:22:32 +0200 Subject: [PATCH 4/5] 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 db321c06f3b6d4cb83d00f841c3cef62b56f2d94 Mon Sep 17 00:00:00 2001 From: MilanGarnier Date: Mon, 7 Sep 2026 13:59:30 +0200 Subject: [PATCH 5/5] 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 =