From bc109ba12630bdeb3e668246f96226686738ed63 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Mon, 1 Dec 2025 15:38:47 +0100 Subject: [PATCH] wip: add poolable map of tag --- .../writer/ddintake/CiTestCycleMapperV1.java | 3 +- .../java/datadog/trace/core/CoreTracer.java | 3 + .../datadog/trace/core/DDSpanContext.java | 22 +- .../java/datadog/trace/core/Metadata.java | 7 +- .../core/tagprocessor/BaseServiceAdder.java | 9 +- .../core/tagprocessor/IntegrationAdder.java | 11 +- .../tagprocessor/PayloadTagsProcessor.java | 20 +- .../tagprocessor/PeerServiceCalculator.java | 18 +- .../core/tagprocessor/PostProcessorChain.java | 9 +- .../core/tagprocessor/QueryObfuscator.java | 13 +- .../tagprocessor/RemoteHostnameAdder.java | 9 +- .../tagprocessor/SpanPointersProcessor.java | 8 +- .../core/tagprocessor/TagsPostProcessor.java | 18 +- .../PostProcessorChainTest.groovy | 12 +- .../datadog/trace/api/SimplePooledMap.java | 356 ++++++++++++++++++ .../trace/api/naming/NamingSchema.java | 4 +- .../api/naming/v0/PeerServiceNamingV0.java | 5 +- .../api/naming/v1/PeerServiceNamingV1.java | 17 +- 18 files changed, 452 insertions(+), 92 deletions(-) create mode 100644 internal-api/src/main/java/datadog/trace/api/SimplePooledMap.java diff --git a/dd-trace-core/src/main/java/datadog/trace/civisibility/writer/ddintake/CiTestCycleMapperV1.java b/dd-trace-core/src/main/java/datadog/trace/civisibility/writer/ddintake/CiTestCycleMapperV1.java index 7593ce9b9dd..ea2321cd366 100644 --- a/dd-trace-core/src/main/java/datadog/trace/civisibility/writer/ddintake/CiTestCycleMapperV1.java +++ b/dd-trace-core/src/main/java/datadog/trace/civisibility/writer/ddintake/CiTestCycleMapperV1.java @@ -9,7 +9,6 @@ import datadog.communication.serialization.msgpack.MsgPackWriter; import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; -import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CiVisibilityWellKnownTags; import datadog.trace.api.civisibility.InstrumentationBridge; import datadog.trace.api.civisibility.telemetry.CiVisibilityDistributionMetric; @@ -317,7 +316,7 @@ MetaWriter withWritable(Writable writable) { @Override public void accept(Metadata metadata) { - TagMap tags = metadata.getTags().copy(); + Map tags = new HashMap<>(metadata.getTags()); for (String ignoredTag : DEFAULT_TOP_LEVEL_TAGS) { tags.remove(ignoredTag); diff --git a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java index 71358bd7dfe..76e4a9d80fd 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java @@ -1256,6 +1256,9 @@ void write(final List trace) { if (null != rootSpan) { onRootSpanFinished(rootSpan, rootSpan.getEndpointTracker()); } + for (DDSpan span : writtenTrace) { + span.context().releaseTags(); + } } private List interceptCompleteTrace(List trace) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java index 4c404ae0a38..07c47c992e0 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java @@ -11,6 +11,7 @@ import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; import datadog.trace.api.ProcessTags; +import datadog.trace.api.SimplePooledMap; import datadog.trace.api.TagMap; import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; @@ -93,6 +94,7 @@ public class DDSpanContext private volatile short httpStatusCode; private CharSequence integrationName; + private final String poolKey; /** * Tags are associated to the current span, they will not propagate to the children span. @@ -103,7 +105,7 @@ public class DDSpanContext * rather read and accessed in a serial fashion on thread after thread. The synchronization can * then be wrapped around bulk operations to minimize the costly atomic operations. */ - private final TagMap unsafeTags; + private final SimplePooledMap unsafeTags; /** The service name is required, otherwise the span are dropped by the agent */ private volatile String serviceName; @@ -112,10 +114,13 @@ public class DDSpanContext private volatile CharSequence resourceName; private volatile byte resourceNamePriority = ResourceNamePriorities.DEFAULT; + /** Each span have an operation name describing the current span */ private volatile CharSequence operationName; + /** The type of the span. If null, the Datadog Agent will report as a custom */ private volatile CharSequence spanType; + /** True indicates that the span reports an error */ private volatile boolean errorFlag; @@ -351,7 +356,8 @@ public DDSpanContext( // The +1 is the magic number from the tags below that we set at the end, // and "* 4 / 3" is to make sure that we don't resize immediately final int capacity = Math.max((tagsSize <= 0 ? 3 : (tagsSize + 1)) * 4 / 3, 8); - this.unsafeTags = TagMap.create(capacity); + this.poolKey = operationName != null ? operationName.toString() : ""; + this.unsafeTags = SimplePooledMap.acquire(poolKey, capacity); // must set this before setting the service and resource names below this.profilingContextIntegration = profilingContextIntegration; @@ -790,7 +796,7 @@ void setAllTags(final TagMap map, boolean needsIntercept) { Object value = tagEntry.objectValue(); if (!tagInterceptor.interceptTag(ctx, tag, value)) { - ctx.unsafeTags.set(tagEntry); + ctx.unsafeTags.put(tag, value); } }); } else { @@ -816,7 +822,7 @@ void setAllTags(final TagMap.Ledger ledger) { Object value = entry.objectValue(); if (!tagInterceptor.interceptTag(this, tag, value)) { - unsafeTags.set(entry); + unsafeTags.put(tag, value); } } } @@ -863,6 +869,10 @@ Object getTag(final String key) { } } + void releaseTags() { + SimplePooledMap.release(poolKey, unsafeTags); + } + /** * This is not thread-safe and must only be used when it can be guaranteed that the context will * not be mutated. This is internal API and must not be exposed to users. @@ -871,13 +881,13 @@ Object getTag(final String key) { * @return the value associated with the tag */ public Object unsafeGetTag(final String tag) { - return unsafeTags.getObject(tag); + return unsafeTags.get(tag); } @Deprecated public TagMap getTags() { synchronized (unsafeTags) { - TagMap tags = unsafeTags.copy(); + TagMap tags = TagMap.fromMap(unsafeTags); tags.put(DDTags.THREAD_ID, threadId); // maintain previously observable type of the thread name :| diff --git a/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java b/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java index d116d19f77f..1ff093cb22b 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/Metadata.java @@ -2,7 +2,6 @@ import static datadog.trace.api.sampling.PrioritySampling.UNSET; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import java.util.Map; @@ -10,7 +9,7 @@ public final class Metadata { private final long threadId; private final UTF8BytesString threadName; private final UTF8BytesString httpStatusCode; - private final TagMap tags; + private final Map tags; private final Map baggage; private final int samplingPriority; @@ -23,7 +22,7 @@ public final class Metadata { public Metadata( long threadId, UTF8BytesString threadName, - TagMap tags, + Map tags, Map baggage, int samplingPriority, boolean measured, @@ -61,7 +60,7 @@ public UTF8BytesString getThreadName() { return threadName; } - public TagMap getTags() { + public Map getTags() { return this.tags; } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/BaseServiceAdder.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/BaseServiceAdder.java index 4a2f2d5377f..8da36ba3d93 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/BaseServiceAdder.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/BaseServiceAdder.java @@ -1,14 +1,14 @@ package datadog.trace.core.tagprocessor; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.core.DDSpanContext; import java.util.List; +import java.util.Map; import javax.annotation.Nullable; -public final class BaseServiceAdder extends TagsPostProcessor { +public final class BaseServiceAdder implements TagsPostProcessor { private final UTF8BytesString ddService; public BaseServiceAdder(@Nullable final String ddService) { @@ -16,13 +16,14 @@ public BaseServiceAdder(@Nullable final String ddService) { } @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { if (ddService != null && spanContext != null && !ddService.toString().equalsIgnoreCase(spanContext.getServiceName())) { unsafeTags.put(DDTags.BASE_SERVICE, ddService); unsafeTags.remove("version"); } + return unsafeTags; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/IntegrationAdder.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/IntegrationAdder.java index 87024d057bd..b0dbdee9ac8 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/IntegrationAdder.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/IntegrationAdder.java @@ -2,20 +2,21 @@ import static datadog.trace.api.DDTags.DD_INTEGRATION; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.core.DDSpanContext; import java.util.List; +import java.util.Map; -public class IntegrationAdder extends TagsPostProcessor { +public class IntegrationAdder implements TagsPostProcessor { @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { final CharSequence instrumentationName = spanContext.getIntegrationName(); if (instrumentationName != null) { - unsafeTags.set(DD_INTEGRATION, instrumentationName); + unsafeTags.put(DD_INTEGRATION, instrumentationName); } else { unsafeTags.remove(DD_INTEGRATION); } + return unsafeTags; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java index 87544b62aeb..c1bd2178842 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java @@ -4,7 +4,6 @@ import datadog.trace.api.Config; import datadog.trace.api.ConfigDefaults; -import datadog.trace.api.TagMap; import datadog.trace.api.telemetry.LogCollector; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.core.DDSpanContext; @@ -22,7 +21,7 @@ import org.slf4j.LoggerFactory; /** Post-processor that extracts tags from payload data injected as tags by instrumentations. */ -public final class PayloadTagsProcessor extends TagsPostProcessor { +public final class PayloadTagsProcessor implements TagsPostProcessor { private static final Logger log = LoggerFactory.getLogger(PayloadTagsProcessor.class); private static final String REDACTED = "redacted"; @@ -70,19 +69,17 @@ public static PayloadTagsProcessor create(Config config) { } @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { int spanMaxTags = maxTags + unsafeTags.size(); for (Map.Entry tagPrefixRedactionRules : redactionRulesByTagPrefix.entrySet()) { String tagPrefix = tagPrefixRedactionRules.getKey(); RedactionRules redactionRules = tagPrefixRedactionRules.getValue(); - Object tagValue = unsafeTags.getObject(tagPrefix); + Object tagValue = unsafeTags.get(tagPrefix); if (tagValue instanceof PayloadTagsData) { - if (unsafeTags.remove(tagPrefix)) { - spanMaxTags -= 1; - } - + unsafeTags.remove(tagPrefix); + spanMaxTags -= 1; PayloadTagsData payloadTagsData = (PayloadTagsData) tagValue; PayloadTagsCollector payloadTagsCollector = new PayloadTagsCollector(maxDepth, spanMaxTags, redactionRules, tagPrefix, unsafeTags); @@ -95,6 +92,7 @@ public void processTags( tagValue); } } + return unsafeTags; } private void collectPayloadTags( @@ -181,14 +179,14 @@ private static final class PayloadTagsCollector implements JsonStreamParser.Visi private final RedactionRules redactionRules; private final String tagPrefix; - private final TagMap collectedTags; + private final Map collectedTags; public PayloadTagsCollector( int maxDepth, int maxTags, RedactionRules redactionRules, String tagPrefix, - TagMap collectedTags) { + Map collectedTags) { this.maxDepth = maxDepth; this.maxTags = maxTags; this.redactionRules = redactionRules; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java index 9a4e9377cb9..fd873443ea1 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java @@ -2,7 +2,6 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; import datadog.trace.api.naming.NamingSchema; import datadog.trace.api.naming.SpanNaming; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; @@ -12,7 +11,7 @@ import java.util.Map; import javax.annotation.Nonnull; -public final class PeerServiceCalculator extends TagsPostProcessor { +public final class PeerServiceCalculator implements TagsPostProcessor { private final NamingSchema.ForPeerService peerServiceNaming; private final Map peerServiceMapping; @@ -33,26 +32,27 @@ public PeerServiceCalculator() { } @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { - Object peerService = unsafeTags.getObject(Tags.PEER_SERVICE); + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + Object peerService = unsafeTags.get(Tags.PEER_SERVICE); // the user set it if (peerService != null) { if (canRemap) { remapPeerService(unsafeTags, peerService); - return; + return unsafeTags; } } else if (peerServiceNaming.supports()) { // calculate the defaults (if any) peerServiceNaming.tags(unsafeTags); // only remap if the mapping is not empty (saves one get) - remapPeerService(unsafeTags, canRemap ? unsafeTags.getObject(Tags.PEER_SERVICE) : null); - return; + remapPeerService(unsafeTags, canRemap ? unsafeTags.get(Tags.PEER_SERVICE) : null); + return unsafeTags; } // we have no peer.service and we do not compute defaults. Leave the map untouched + return unsafeTags; } - private void remapPeerService(TagMap unsafeTags, Object value) { + private void remapPeerService(Map unsafeTags, Object value) { if (value != null) { String mapped = peerServiceMapping.get(value); if (mapped != null) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PostProcessorChain.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PostProcessorChain.java index 77374d742cb..7a099f3b98e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PostProcessorChain.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PostProcessorChain.java @@ -1,13 +1,13 @@ package datadog.trace.core.tagprocessor; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.core.DDSpanContext; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.annotation.Nonnull; -public final class PostProcessorChain extends TagsPostProcessor { +public final class PostProcessorChain implements TagsPostProcessor { private final TagsPostProcessor[] chain; public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { @@ -15,10 +15,11 @@ public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { } @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { for (final TagsPostProcessor tagsPostProcessor : chain) { tagsPostProcessor.processTags(unsafeTags, spanContext, spanLinks); } + return unsafeTags; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/QueryObfuscator.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/QueryObfuscator.java index 37bbd470596..7ed6620a5c5 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/QueryObfuscator.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/QueryObfuscator.java @@ -4,16 +4,16 @@ import com.google.re2j.Pattern; import com.google.re2j.PatternSyntaxException; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.DDSpanContext; import datadog.trace.util.Strings; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public final class QueryObfuscator extends TagsPostProcessor { +public final class QueryObfuscator implements TagsPostProcessor { private static final Logger log = LoggerFactory.getLogger(QueryObfuscator.class); @@ -58,18 +58,19 @@ private String obfuscate(String query) { } @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { - Object query = unsafeTags.getObject(DDTags.HTTP_QUERY); + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + Object query = unsafeTags.get(DDTags.HTTP_QUERY); if (query instanceof CharSequence) { query = obfuscate(query.toString()); unsafeTags.put(DDTags.HTTP_QUERY, query); - Object url = unsafeTags.getObject(Tags.HTTP_URL); + Object url = unsafeTags.get(Tags.HTTP_URL); if (url instanceof CharSequence) { unsafeTags.put(Tags.HTTP_URL, url + "?" + query); } } + return unsafeTags; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/RemoteHostnameAdder.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/RemoteHostnameAdder.java index 7bd45cd2c92..e6688f4aa99 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/RemoteHostnameAdder.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/RemoteHostnameAdder.java @@ -1,13 +1,13 @@ package datadog.trace.core.tagprocessor; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.core.DDSpanContext; import java.util.List; +import java.util.Map; import java.util.function.Supplier; -public final class RemoteHostnameAdder extends TagsPostProcessor { +public final class RemoteHostnameAdder implements TagsPostProcessor { private final Supplier hostnameSupplier; public RemoteHostnameAdder(Supplier hostnameSupplier) { @@ -15,10 +15,11 @@ public RemoteHostnameAdder(Supplier hostnameSupplier) { } @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { if (spanContext.getSpanId() == spanContext.getRootSpanId()) { unsafeTags.put(DDTags.TRACER_HOST, hostnameSupplier.get()); } + return unsafeTags; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/SpanPointersProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/SpanPointersProcessor.java index 8282583cbf2..7bc971f0203 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/SpanPointersProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/SpanPointersProcessor.java @@ -11,7 +11,6 @@ import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.DYNAMO_PRIMARY_KEY_2_VALUE; import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.S3_ETAG; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.SpanAttributes; import datadog.trace.bootstrap.instrumentation.api.SpanLink; @@ -26,7 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SpanPointersProcessor extends TagsPostProcessor { +public class SpanPointersProcessor implements TagsPostProcessor { private static final Logger LOG = LoggerFactory.getLogger(SpanPointersProcessor.class); // The pointer direction will always be down. The serverless agent handles cases where the @@ -37,8 +36,8 @@ public class SpanPointersProcessor extends TagsPostProcessor { public static final String LINK_KIND = "span-pointer"; @Override - public void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks) { // DQH - TODO - There's a lot room to optimize this using TagMap's capabilities AgentSpanLink s3Link = handleS3SpanPointer(unsafeTags); if (s3Link != null) { @@ -49,6 +48,7 @@ public void processTags( if (dynamoDbLink != null) { spanLinks.add(dynamoDbLink); } + return unsafeTags; } private static AgentSpanLink handleS3SpanPointer(Map unsafeTags) { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessor.java index d0acedb40ee..f188e10d090 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/TagsPostProcessor.java @@ -1,23 +1,11 @@ package datadog.trace.core.tagprocessor; -import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.core.DDSpanContext; import java.util.List; import java.util.Map; -public abstract class TagsPostProcessor { - /* - * DQH - For testing purposes only - */ - @Deprecated - final Map processTags( - Map unsafeTags, DDSpanContext context, List links) { - TagMap map = TagMap.fromMap(unsafeTags); - this.processTags(map, context, links); - return map; - } - - public abstract void processTags( - TagMap unsafeTags, DDSpanContext spanContext, List spanLinks); +public interface TagsPostProcessor { + Map processTags( + Map unsafeTags, DDSpanContext spanContext, List spanLinks); } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/tagprocessor/PostProcessorChainTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/tagprocessor/PostProcessorChainTest.groovy index 8961c8d41f8..f73815450b1 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/tagprocessor/PostProcessorChainTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/tagprocessor/PostProcessorChainTest.groovy @@ -10,15 +10,17 @@ class PostProcessorChainTest extends DDSpecification { setup: def processor1 = new TagsPostProcessor() { @Override - void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { unsafeTags.put("key1", "processor1") unsafeTags.put("key2", "processor1") + unsafeTags } } def processor2 = new TagsPostProcessor() { @Override - void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { unsafeTags.put("key1", "processor2") + unsafeTags } } @@ -36,17 +38,19 @@ class PostProcessorChainTest extends DDSpecification { setup: def processor1 = new TagsPostProcessor() { @Override - void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { unsafeTags.clear() unsafeTags.put("my", "tag") + unsafeTags } } def processor2 = new TagsPostProcessor() { @Override - void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { if (unsafeTags.containsKey("test")) { unsafeTags.put("found", "true") } + unsafeTags } } diff --git a/internal-api/src/main/java/datadog/trace/api/SimplePooledMap.java b/internal-api/src/main/java/datadog/trace/api/SimplePooledMap.java new file mode 100644 index 00000000000..32bad617a25 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/SimplePooledMap.java @@ -0,0 +1,356 @@ +package datadog.trace.api; + +import java.lang.ref.WeakReference; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentHashMap; + +public final class SimplePooledMap extends AbstractMap { + + /** Weak pooled values per poolKey */ + private static final Map>> POOLS = + new ConcurrentHashMap<>(); + + /** Max number of retained entries per poolKey */ + private static final int MAX_RETAINED = 128; + + /** Acquire an instance, reusing from pool if possible */ + public static SimplePooledMap acquire(String poolKey, int initialCapacity) { + ArrayBlockingQueue> queue = + POOLS.computeIfAbsent(poolKey, k -> new ArrayBlockingQueue<>(MAX_RETAINED)); + + while (true) { + WeakReference ref = queue.poll(); + if (ref == null) { + break; // nothing reusable + } + SimplePooledMap map = ref.get(); + if (map != null) { + map.clear(); + return map; + } + // dead ref → continue polling + } + + // Nothing reusable: create new instance + return new SimplePooledMap(initialCapacity); + } + + /** Return instance to pool. */ + public static void release(String poolKey, SimplePooledMap map) { + ArrayBlockingQueue> queue = + POOLS.computeIfAbsent(poolKey, k -> new ArrayBlockingQueue<>(MAX_RETAINED)); + + // Offer the map back if there's space; otherwise, drop it + queue.offer(new WeakReference<>(map)); + } + + private String[] keys; + private Object[] values; + private boolean[] visible; + + /** Occupied slot indexes — avoids scanning whole table */ + private int[] occupied; + + private int occCount; + + private int capacity; + private int mask; + private int size; + private final float loadFactor; + + public SimplePooledMap(int initialCapacity) { + this(initialCapacity, 0.60f); + } + + public SimplePooledMap(int initialCapacity, float loadFactor) { + this.loadFactor = loadFactor; + this.capacity = tableSizeFor(initialCapacity); + this.mask = capacity - 1; + this.keys = new String[capacity]; + this.values = new Object[capacity]; + this.visible = new boolean[capacity]; + this.occupied = new int[capacity]; + this.occCount = 0; + this.size = 0; + } + + /** Next power-of-two */ + private static int tableSizeFor(int n) { + if (n <= 1) { + return 1; + } + return 1 << (32 - Integer.numberOfLeadingZeros(n - 1)); + } + + private static int spread(int h) { + return (h ^ (h >>> 16)); + } + + /** Locate slot using linear probing */ + private int findSlot(String key, String[] table) { + int idx = spread(key.hashCode()) & (table.length - 1); + while (true) { + String k = table[idx]; + if (k == null || k.equals(key)) { + return idx; + } + idx = (idx + 1) & (table.length - 1); + } + } + + private void ensureCapacity() { + if ((size + 1) >= (int) (capacity * loadFactor)) { + grow(); + } + } + + private void grow() { + int newCap = capacity << 1; + + String[] oldKeys = keys; + Object[] oldValues = values; + boolean[] oldVisible = visible; + int[] oldOccupied = occupied; + int oldOccCount = occCount; + + keys = new String[newCap]; + values = new Object[newCap]; + visible = new boolean[newCap]; + occupied = new int[newCap]; + + capacity = newCap; + mask = newCap - 1; + occCount = 0; + size = 0; + + // Only reinsert visible entries + for (int i = 0; i < oldOccCount; i++) { + int oldIdx = oldOccupied[i]; + if (oldVisible[oldIdx]) { + String k = oldKeys[oldIdx]; + Object v = oldValues[oldIdx]; + + int idx = findSlot(k, keys); + keys[idx] = k; + values[idx] = v; + visible[idx] = true; + + occupied[occCount++] = idx; + size++; + } + } + } + + @Override + public Object put(String key, Object value) { + ensureCapacity(); + + int idx = spread(key.hashCode()) & mask; + + // Fast-path linear probing + while (true) { + String k = keys[idx]; + + if (k == null) { + // First insertion + keys[idx] = key; + values[idx] = value; + visible[idx] = true; + + occupied[occCount++] = idx; + size++; + return null; + } + + if (k.equals(key)) { + if (!visible[idx]) { + // Key exists but hidden + values[idx] = value; + visible[idx] = true; + size++; + return null; + } else { + // Key exists and visible + Object old = values[idx]; + values[idx] = value; + return old; + } + } + + idx = (idx + 1) & mask; + } + } + + @Override + public Object get(Object keyObj) { + if (!(keyObj instanceof String)) { + return null; + } + + String key = (String) keyObj; + int idx = spread(key.hashCode()) & mask; + + while (true) { + String k = keys[idx]; + if (k == null) { + return null; + } + if (k.equals(key)) { + if (visible[idx]) { + return values[idx]; + } else { + return null; + } + } + idx = (idx + 1) & mask; + } + } + + @Override + public boolean containsKey(Object keyObj) { + if (!(keyObj instanceof String)) { + return false; + } + + String key = (String) keyObj; + int idx = spread(key.hashCode()) & mask; + + while (true) { + String k = keys[idx]; + if (k == null) { + return false; + } + if (k.equals(key)) { + return visible[idx]; + } + idx = (idx + 1) & mask; + } + } + + // ⚡ remove without clearing values[] + @Override + public Object remove(Object keyObj) { + if (!(keyObj instanceof String)) { + return null; + } + + String key = (String) keyObj; + int idx = spread(key.hashCode()) & mask; + + while (true) { + String k = keys[idx]; + if (k == null) { + return null; + } + if (k.equals(key)) { + if (visible[idx]) { + visible[idx] = false; + size--; + return values[idx]; + } else { + return null; + } + } + idx = (idx + 1) & mask; + } + } + + @Override + public int size() { + return size; + } + + /** Clear only visible entries — O(size), not O(capacity) */ + @Override + public void clear() { + for (int i = 0; i < occCount; i++) { + int idx = occupied[i]; + if (visible[idx]) { + visible[idx] = false; + } + } + size = 0; + } + + @Override + public Set> entrySet() { + return new EntrySetView(); + } + + private final class EntrySetView extends AbstractSet> { + + @Override + public int size() { + return SimplePooledMap.this.size; + } + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + } + + /** GC-free reusable entry */ + private static final class ReusableEntry implements Entry { + private String key; + private Object value; + + private void reset(String k, Object v) { + this.key = k; + this.value = v; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException("Mutable entry not supported"); + } + } + + private final class EntryIterator implements Iterator> { + private int pos = 0; + private final ReusableEntry entry = new ReusableEntry(); + + @Override + public boolean hasNext() { + while (pos < occCount) { + int idx = occupied[pos]; + if (visible[idx]) { + return true; + } else { + pos++; + } + } + return false; + } + + @Override + public Entry next() { + while (pos < occCount && !visible[occupied[pos]]) { + pos++; + } + if (pos >= occCount) { + throw new NoSuchElementException(); + } + + int idx = occupied[pos++]; + entry.reset(keys[idx], values[idx]); + return entry; + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java b/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java index 31b610887ee..9047534073d 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/NamingSchema.java @@ -1,6 +1,6 @@ package datadog.trace.api.naming; -import datadog.trace.api.TagMap; +import java.util.Map; import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -232,7 +232,7 @@ interface ForPeerService { * @param unsafeTags the span tags. Map to be mutated */ @Nonnull - void tags(@Nonnull TagMap unsafeTags); + void tags(@Nonnull Map unsafeTags); } interface ForServer { diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java b/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java index 3e76d657069..c23be4b23fc 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v0/PeerServiceNamingV0.java @@ -1,7 +1,7 @@ package datadog.trace.api.naming.v0; -import datadog.trace.api.TagMap; import datadog.trace.api.naming.NamingSchema; +import java.util.Map; import javax.annotation.Nonnull; public class PeerServiceNamingV0 implements NamingSchema.ForPeerService { @@ -10,7 +10,6 @@ public boolean supports() { return false; } - @Nonnull @Override - public void tags(@Nonnull final TagMap unsafeTags) {} + public void tags(@Nonnull final Map unsafeTags) {} } diff --git a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java index 827a7489e09..e38f72202b0 100644 --- a/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java +++ b/internal-api/src/main/java/datadog/trace/api/naming/v1/PeerServiceNamingV1.java @@ -1,7 +1,6 @@ package datadog.trace.api.naming.v1; import datadog.trace.api.DDTags; -import datadog.trace.api.TagMap; import datadog.trace.api.naming.NamingSchema; import datadog.trace.bootstrap.instrumentation.api.InstrumentationTags; import datadog.trace.bootstrap.instrumentation.api.Tags; @@ -53,8 +52,8 @@ public boolean supports() { return true; } - private void resolve(@Nonnull final TagMap unsafeTags) { - final Object component = unsafeTags.getObject(Tags.COMPONENT); + private void resolve(@Nonnull final Map unsafeTags) { + final Object component = unsafeTags.get(Tags.COMPONENT); // avoid issues with UTF8ByteString or others final String componentString = component == null ? null : component.toString(); final String override = overridesByComponent.get(componentString); @@ -71,14 +70,15 @@ private void resolve(@Nonnull final TagMap unsafeTags) { resolveBy(unsafeTags, DEFAULT_PRECURSORS); } - private boolean resolveBy(@Nonnull final TagMap unsafeTags, @Nullable final String[] precursors) { + private boolean resolveBy( + @Nonnull final Map unsafeTags, @Nullable final String[] precursors) { if (precursors == null) { return false; } Object value = null; String source = null; for (String precursor : precursors) { - value = unsafeTags.getObject(precursor); + value = unsafeTags.get(precursor); if (value != null) { // we have a match. Use the tag name for the source source = precursor; @@ -90,18 +90,17 @@ private boolean resolveBy(@Nonnull final TagMap unsafeTags, @Nullable final Stri return true; } - private void set(@Nonnull final TagMap unsafeTags, Object value, String source) { + private void set(@Nonnull final Map unsafeTags, Object value, String source) { if (value != null) { unsafeTags.put(Tags.PEER_SERVICE, value); unsafeTags.put(DDTags.PEER_SERVICE_SOURCE, source); } } - @Nonnull @Override - public void tags(@Nonnull final TagMap unsafeTags) { + public void tags(@Nonnull final Map unsafeTags) { // check span.kind eligibility - final Object kind = unsafeTags.getObject(Tags.SPAN_KIND); + final Object kind = unsafeTags.get(Tags.SPAN_KIND); if (Tags.SPAN_KIND_CLIENT.equals(kind) || Tags.SPAN_KIND_PRODUCER.equals(kind)) { // we can calculate the peer service now resolve(unsafeTags);