diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java index 4d86f94fec6..c0767b7bcfd 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/DatadogPTagsCodec.java @@ -102,7 +102,7 @@ PropagationTags fromHeaderValue(PTagsFactory tagsFactory, String value, int samp return tagsFactory.createInvalid(PROPAGATION_ERROR_DECODING_ERROR); } TagKey tagKey = TagKey.from(Encoding.DATADOG, value, tagPos, tagKeyEndsAt); - TagValue tagValue = TagValue.from(Encoding.DATADOG, value, tagValuePos, tagValueEndsAt); + TagValue tagValue = tagValue(Encoding.DATADOG, tagKey, value, tagValuePos, tagValueEndsAt); if (tagKey != null) { if (!tagKey.equals(UPSTREAM_SERVICES_DEPRECATED_TAG)) { if (!validateTagValue(tagKey, tagValue)) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsCodec.java index 3dd2e248442..29867d08d83 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsCodec.java @@ -31,9 +31,26 @@ abstract class PTagsCodec { protected static final TagKey LLMOBS_PAGENT_SPAN_ID_TAG = TagKey.from("llmobs_pagent_span_id"); protected static final TagKey LLMOBS_PAGENT_NAME_TAG = TagKey.from("llmobs_pagent_name"); protected static final TagKey LLMOBS_PARENT_ID_TAG = TagKey.from("llmobs_parent_id"); + protected static final TagKey LLMOBS_SAMPLE_RATE_TAG = TagKey.from("llmobs_sr"); protected static final TagKey LLMOBS_SAMPLING_DECISION_TAG = TagKey.from("llmobs_sd"); + /** + * The value of tag {@code key}, cached unless the key's values are unique to one trace or span. + */ + static TagValue tagValue(Encoding encoding, TagKey key, CharSequence s, int start, int end) { + return hasUniqueValues(key) + ? TagValue.uncached(encoding, s, start, end) + : TagValue.from(encoding, s, start, end); + } + + /** Whether values of {@code key} are unique to one trace or span, so not worth caching. */ + static boolean hasUniqueValues(TagKey key) { + return LLMOBS_TRACE_ID_TAG.equals(key) + || LLMOBS_PARENT_ID_TAG.equals(key) + || LLMOBS_PAGENT_SPAN_ID_TAG.equals(key); + } + /** * Encodes the header. {@code llmObsValues} are the LLM Observability values of the span being * injected; {@code null} writes the ones that arrived on the inbound headers instead, which is diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/TagValue.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/TagValue.java index a6c5f1bbf0b..d1610bc62d3 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/TagValue.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/TagValue.java @@ -20,10 +20,7 @@ static TagValue from(Encoding encoding, CharSequence s) { } static TagValue from(Encoding encoding, CharSequence s, int start, int end) { - if (s == null || isValueInvalid(s, start, end)) { - if (log.isDebugEnabled()) { - log.debug("Invalid header h: {} s: {} b: {} e: {}", encoding, s, start, end); - } + if (isInvalid(encoding, s, start, end)) { return null; } if (encoding == Encoding.W3C) { @@ -35,6 +32,29 @@ static TagValue from(Encoding encoding, CharSequence s, int start, int end) { } } + /** + * Like {@link #from(Encoding, CharSequence, int, int)}, for a value unique to one trace or span, + * such as a span id. Caching those would never hit, and would evict values that do repeat. + */ + static TagValue uncached(Encoding encoding, CharSequence s, int start, int end) { + if (isInvalid(encoding, s, start, end)) { + return null; + } + return encoding == Encoding.W3C + ? produceW3C(s, hashW3C(s, start, end), start, end) + : produceDD(s, hashDD(s, start, end), start, end); + } + + private static boolean isInvalid(Encoding encoding, CharSequence s, int start, int end) { + if (s == null || isValueInvalid(s, start, end)) { + if (log.isDebugEnabled()) { + log.debug("Invalid header h: {} s: {} b: {} e: {}", encoding, s, start, end); + } + return true; + } + return false; + } + private static boolean isValueInvalid(CharSequence s, int start, int end) { return start < 0 || end <= 0 || s.length() < end; } 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 49db92a3ab9..9f292518b89 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 @@ -194,11 +194,12 @@ private static R parse(String value, Result result) { } else if (keyLength == 1 && c == 'o') { origin = TagValue.from(Encoding.W3C, value, tagValuePos, tagValueEndsAt); } else if (keyLength == 1 && c == 'p') { - lastParentId = TagValue.from(Encoding.W3C, value, tagValuePos, tagValueEndsAt); + // a span id, so it never repeats + lastParentId = TagValue.uncached(Encoding.W3C, value, tagValuePos, tagValueEndsAt); } else { TagKey tagKey = TagKey.from(Encoding.W3C, value, tagPos, tagKeyEndsAt); if (tagKey != null) { - TagValue tagValue = TagValue.from(Encoding.W3C, value, tagValuePos, tagValueEndsAt); + TagValue tagValue = tagValue(Encoding.W3C, tagKey, value, tagValuePos, tagValueEndsAt); if (!tagKey.equals(UPSTREAM_SERVICES_DEPRECATED_TAG)) { if (!validateTagValue(tagKey, tagValue)) { log.warn( diff --git a/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/UniqueTagValueCachingTest.java b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/UniqueTagValueCachingTest.java new file mode 100644 index 00000000000..6745961b865 --- /dev/null +++ b/dd-trace-core/src/test/java/datadog/trace/core/propagation/ptags/UniqueTagValueCachingTest.java @@ -0,0 +1,57 @@ +package datadog.trace.core.propagation.ptags; + +import static datadog.trace.core.propagation.PropagationTags.HeaderType.DATADOG; +import static datadog.trace.core.propagation.PropagationTags.HeaderType.W3C; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import datadog.trace.core.propagation.PropagationTags; +import org.junit.jupiter.api.Test; + +/** Values unique to a trace or span skip the tag value cache; values that repeat use it. */ +class UniqueTagValueCachingTest { + private static final PropagationTags.Factory FACTORY = PropagationTags.factory(); + + @Test + void lastParentIdIsNotCached() { + CharSequence first = + FACTORY.fromHeaderValue(W3C, "dd=s:1;p:00f067aa0ba902b7").getLastParentId(); + CharSequence second = + FACTORY.fromHeaderValue(W3C, "dd=s:1;p:00f067aa0ba902b7").getLastParentId(); + + assertEquals(first, second); + assertNotSame(first, second); + } + + @Test + void uncachedValueEqualsCachedValue() { + TagValue cached = TagValue.from(TagElement.Encoding.W3C, "x:abc", 2, 5); + TagValue uncached = TagValue.uncached(TagElement.Encoding.W3C, "x:abc", 2, 5); + + assertEquals(cached, uncached); + assertEquals(cached.hashCode(), uncached.hashCode()); + assertEquals( + cached.forType(TagElement.Encoding.DATADOG), uncached.forType(TagElement.Encoding.DATADOG)); + } + + @Test + void repeatedValuesAreStillCached() { + TagValue first = TagValue.from(TagElement.Encoding.DATADOG, "-4"); + TagValue second = TagValue.from(TagElement.Encoding.DATADOG, "-4"); + + assertSame(first, second); + } + + @Test + void llmObsIdsParseTheSameUncached() { + String header = "_dd.p.llmobs_trace_id=0123456789abcdef0123456789abcdef,_dd.p.dm=-1"; + + PropagationTags tags = FACTORY.fromHeaderValue(DATADOG, header); + + assertEquals( + "0123456789abcdef0123456789abcdef", + tags.createTagMap().get("_dd.p.llmobs_trace_id"), + tags.createTagMap().toString()); + } +}