Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -540,7 +542,20 @@ public String getW3CTracestate() {

@Override
public void updateW3CTracestate(String tracestate) {
clearCachedHeader(W3C);
this.tracestate = tracestate;
setOtelTraceState(W3CPTagsCodec.extractOtelTraceState(tracestate));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reuse the parsed OTel state during reconciliation

perf: With the default DATADOG,TRACECONTEXT extraction order, matching Datadog and W3C headers cause fromHeaderValue to parse the tracestate before HttpCodec.applyTraceContextToFirstContext passes the same string here. Calling extractOtelTraceState then validates and scans it again—and allocates another substring/state when ot is present—on the inbound request path. Transfer the already-parsed state with the propagation tags instead of reparsing the raw header.

AGENTS.md reference: AGENTS.md:L76-L79

Useful? React with 👍 / 👎.

}

OtelTraceState getOtelTraceState() {
return otelTraceState;
}

void setOtelTraceState(OtelTraceState otelTraceState) {
if (this.otelTraceState != otelTraceState) {
this.otelTraceState = otelTraceState;
clearCachedHeader(W3C);
}
}

String getError() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ';';
Expand Down Expand Up @@ -49,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;
}
Expand All @@ -68,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?
Expand All @@ -76,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?
Expand All @@ -84,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);
return empty(tagsFactory, value, otelTraceState);
}

List<TagElement> tagPairs = null;
Expand Down Expand Up @@ -158,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;
Expand Down Expand Up @@ -201,7 +230,8 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value) {
ddMemberValueEnd,
maxUnknownSize,
lastParentId,
orgPropagationMarkerTagValue);
orgPropagationMarkerTagValue,
otelTraceState);
}

@Override
Expand All @@ -226,6 +256,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;
Comment on lines +259 to +261

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid counting the OTel member twice in the buffer estimate

perf: When the original tracestate length is already included above—either for a parsed W3CPTags containing dd or for a Datadog PTags updated during multi-style reconciliation—it already contains the ot= member and its delimiter. Adding its size again here over-allocates the StringBuilder backing storage by up to 260 characters on each fresh W3C encoding, including per-span encodings that use a last-parent override. Subtract the managed member from the original-size contribution or omit this second addition in those cases.

AGENTS.md reference: AGENTS.md:L76-L79

Useful? React with 👍 / 👎.

}
return size;
}

Expand Down Expand Up @@ -290,9 +324,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);
Expand Down Expand Up @@ -698,58 +731,113 @@ 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(
PTagsFactory factory,
String original,
int firstMemberStart,
int ddMemberStart,
int ddMemberValueEnd) {
int ddMemberValueEnd,
OtelTraceState otelTraceState) {
return new W3CPTags(
factory,
null,
Expand All @@ -764,7 +852,8 @@ private static W3CPTags empty(
ddMemberValueEnd,
0,
null,
null);
null,
otelTraceState);
}

private static class W3CPTags extends PTags {
Expand Down Expand Up @@ -799,7 +888,8 @@ public W3CPTags(
int ddMemberValueEnd,
int maxUnknownSize,
CharSequence lastParentId,
TagValue orgPropagationMarkerTagValue) {
TagValue orgPropagationMarkerTagValue,
OtelTraceState otelTraceState) {
super(
factory,
tagPairs,
Expand All @@ -815,6 +905,7 @@ public W3CPTags(
this.ddMemberStart = ddMemberStart;
this.ddMemberValueEnd = ddMemberValueEnd;
this.maxUnknownSize = maxUnknownSize;
setOtelTraceState(otelTraceState);
}

@Override
Expand Down