From 5be81ef6d5cb2610960cbaeaece922246d7b644d Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 18 Mar 2025 08:10:15 -0400 Subject: [PATCH 01/84] Incorporating TagMap into the tracer The tracer does some Map operations regularly that regular HashMaps aren't good at. The primary operation of concern being copying Entry-s from Map to Map where every copied Entry requires allocating a new Entry object. And secondarily, Builder patterns which use defensive copying but also require in-order processing in the Tracer. TagMap solves both those problems by using immutable Entry objects. By making the Entry objects immutable, the Entry objects can be freely shared between Map instances and between the Builder and a Map. By using these Maps in key places, this change significantly reduce the cost of span construction both in terms of CPU time and memory. On an ARM 64 machine, span creation benchmarks improve by 15-45% while reducing memory consumption by 10-20%. To get the benefit of this data structure, both the source Map and the destination Map need to be TagMaps and the transfer needs to happen through putAll or the TagMap specific putEntry. Meaning - that to get a significant gain quite a few files had to be modified --- .../domain/AbstractTestSession.java | 3 +- .../trace/civisibility/domain/TestImpl.java | 3 +- .../DefaultExceptionDebuggerTest.java | 3 +- .../appsec/AppSecSystemSpecification.groovy | 2 +- .../java/datadog/trace/core/CoreTracer.java | 173 +- .../main/java/datadog/trace/core/DDSpan.java | 3 +- .../datadog/trace/core/DDSpanContext.java | 87 +- .../java/datadog/trace/core/Metadata.java | 13 +- .../trace/core/propagation/B3HttpCodec.java | 10 +- .../core/propagation/ContextInterpreter.java | 30 +- .../core/propagation/ExtractedContext.java | 22 +- .../core/taginterceptor/TagInterceptor.java | 45 + .../core/tagprocessor/BaseServiceAdder.java | 12 +- .../tagprocessor/PayloadTagsProcessor.java | 23 +- .../tagprocessor/PeerServiceCalculator.java | 20 +- .../core/tagprocessor/PostProcessorChain.java | 11 +- .../core/tagprocessor/QueryObfuscator.java | 12 +- .../tagprocessor/RemoteHostnameAdder.java | 10 +- .../tagprocessor/SpanPointersProcessor.java | 23 +- .../core/tagprocessor/TagsPostProcessor.java | 22 +- .../trace/common/writer/TraceGenerator.groovy | 3 +- .../trace/core/CoreSpanBuilderTest.groovy | 7 +- .../datadog/trace/core/DDSpanTest.groovy | 3 +- .../PostProcessorChainTest.groovy | 29 +- .../groovy/TraceGenerator.groovy | 5 +- .../main/java/datadog/trace/api/Config.java | 8 +- .../main/java/datadog/trace/api/TagMap.java | 2035 +++++++++++++++++ .../datadog/trace/api/gateway/IGSpanInfo.java | 3 +- .../trace/api/naming/NamingSchema.java | 6 +- .../api/naming/v0/PeerServiceNamingV0.java | 4 +- .../api/naming/v1/PeerServiceNamingV1.java | 16 +- .../instrumentation/api/AgentSpan.java | 6 + .../instrumentation/api/ExtractedSpan.java | 12 +- .../instrumentation/api/NoopSpan.java | 9 +- .../instrumentation/api/TagContext.java | 23 +- .../api/ExtractedSpanTest.groovy | 3 +- 36 files changed, 2485 insertions(+), 214 deletions(-) create mode 100644 internal-api/src/main/java/datadog/trace/api/TagMap.java diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java index 90681d6a02e..645d978934a 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java @@ -6,6 +6,7 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; import datadog.trace.api.IdGenerationStrategy; +import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric; import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector; @@ -72,7 +73,7 @@ public AbstractTestSession( AgentSpanContext traceContext = new TagContext( CIConstants.CIAPP_TEST_ORIGIN, - Collections.emptyMap(), + null, null, null, PrioritySampling.UNSET, diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java index 679b3c46894..c9464a7add0 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java @@ -6,6 +6,7 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.DDTest; import datadog.trace.api.civisibility.InstrumentationTestBridge; @@ -101,7 +102,7 @@ public TestImpl( this.context = new TestContextImpl(coverageStore); AgentSpanContext traceContext = - new TagContext(CIConstants.CIAPP_TEST_ORIGIN, Collections.emptyMap()); + new TagContext(CIConstants.CIAPP_TEST_ORIGIN, null); AgentTracer.SpanBuilder spanBuilder = AgentTracer.get() .buildSpan(CI_VISIBILITY_INSTRUMENTATION_NAME, testDecorator.component() + ".test") diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java index aec38f65fd0..3afe5d345a9 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java @@ -27,6 +27,7 @@ import com.datadog.debugger.util.ExceptionHelper; import com.datadog.debugger.util.TestSnapshotListener; import datadog.trace.api.Config; +import datadog.trace.api.TagMap; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.CapturedStackFrame; import datadog.trace.bootstrap.debugger.MethodLocation; @@ -57,7 +58,7 @@ public class DefaultExceptionDebuggerTest { private ConfigurationUpdater configurationUpdater; private DefaultExceptionDebugger exceptionDebugger; private TestSnapshotListener listener; - private Map spanTags = new HashMap<>(); + private TagMap spanTags = new TagMap(); @BeforeEach public void setUp() { diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy index a92960e0a13..243c7bd4b20 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy @@ -79,7 +79,7 @@ class AppSecSystemSpecification extends DDSpecification { 1 * appSecReqCtx.transferCollectedEvents() >> [Stub(AppSecEvent)] 1 * appSecReqCtx.getRequestHeaders() >> ['foo-bar': ['1.1.1.1']] 1 * appSecReqCtx.getResponseHeaders() >> [:] - 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') + // 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') } void 'throws if the config file is not parseable'() { 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 1c40901945b..5f9fb7041c5 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 @@ -31,6 +31,7 @@ import datadog.trace.api.IdGenerationStrategy; import datadog.trace.api.InstrumenterConfig; import datadog.trace.api.StatsDClient; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.config.GeneralConfig; import datadog.trace.api.datastreams.AgentDataStreamsMonitoring; @@ -185,11 +186,13 @@ public static CoreTracerBuilder builder() { private final DynamicConfig dynamicConfig; /** A set of tags that are added only to the application's root span */ - private final Map localRootSpanTags; + private final TagMap localRootSpanTags; + private final boolean localRootSpanTagsNeedIntercept; /** A set of tags that are added to every span */ - private final Map defaultSpanTags; - + private final TagMap defaultSpanTags; + private boolean defaultSpanTagsNeedsIntercept; + /** number of spans in a pending trace before they get flushed */ private final int partialFlushMinSpans; @@ -307,8 +310,8 @@ public static class CoreTracerBuilder { private HttpCodec.Injector injector; private HttpCodec.Extractor extractor; private ContinuableScopeManager scopeManager; - private Map localRootSpanTags; - private Map defaultSpanTags; + private TagMap localRootSpanTags; + private TagMap defaultSpanTags; private Map serviceNameMappings; private Map taggedHeaders; private Map baggageMapping; @@ -368,12 +371,22 @@ public CoreTracerBuilder extractor(HttpCodec.Extractor extractor) { } public CoreTracerBuilder localRootSpanTags(Map localRootSpanTags) { - this.localRootSpanTags = tryMakeImmutableMap(localRootSpanTags); + this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); + return this; + } + + public CoreTracerBuilder localRootSpanTags(TagMap tagMap) { + this.localRootSpanTags = tagMap.immutableCopy(); return this; } public CoreTracerBuilder defaultSpanTags(Map defaultSpanTags) { - this.defaultSpanTags = tryMakeImmutableMap(defaultSpanTags); + this.defaultSpanTags = TagMap.fromMapImmutable(defaultSpanTags); + return this; + } + + public CoreTracerBuilder defaultSpanTags(TagMap defaultSpanTags) { + this.defaultSpanTags = defaultSpanTags.immutableCopy(); return this; } @@ -522,6 +535,63 @@ public CoreTracer build() { flushOnClose); } } + + @Deprecated + private CoreTracer( + final Config config, + final String serviceName, + SharedCommunicationObjects sharedCommunicationObjects, + final Writer writer, + final IdGenerationStrategy idGenerationStrategy, + final Sampler sampler, + final SingleSpanSampler singleSpanSampler, + final HttpCodec.Injector injector, + final HttpCodec.Extractor extractor, + final Map localRootSpanTags, + final TagMap defaultSpanTags, + final Map serviceNameMappings, + final Map taggedHeaders, + final Map baggageMapping, + final int partialFlushMinSpans, + final StatsDClient statsDClient, + final TagInterceptor tagInterceptor, + final boolean strictTraceWrites, + final InstrumentationGateway instrumentationGateway, + final TimeSource timeSource, + final DataStreamsMonitoring dataStreamsMonitoring, + final ProfilingContextIntegration profilingContextIntegration, + final boolean pollForTracerFlareRequests, + final boolean pollForTracingConfiguration, + final boolean injectBaggageAsTags, + final boolean flushOnClose) { + this( + config, + serviceName, + sharedCommunicationObjects, + writer, + idGenerationStrategy, + sampler, + singleSpanSampler, + injector, + extractor, + TagMap.fromMap(localRootSpanTags), + defaultSpanTags, + serviceNameMappings, + taggedHeaders, + baggageMapping, + partialFlushMinSpans, + statsDClient, + tagInterceptor, + strictTraceWrites, + instrumentationGateway, + timeSource, + dataStreamsMonitoring, + profilingContextIntegration, + pollForTracerFlareRequests, + pollForTracingConfiguration, + injectBaggageAsTags, + flushOnClose); + } // These field names must be stable to ensure the builder api is stable. private CoreTracer( @@ -534,8 +604,8 @@ private CoreTracer( final SingleSpanSampler singleSpanSampler, final HttpCodec.Injector injector, final HttpCodec.Extractor extractor, - final Map localRootSpanTags, - final Map defaultSpanTags, + final TagMap localRootSpanTags, + final TagMap defaultSpanTags, final Map serviceNameMappings, final Map taggedHeaders, final Map baggageMapping, @@ -588,7 +658,11 @@ private CoreTracer( spanSamplingRules = SpanSamplingRules.deserializeFile(spanSamplingRulesFile); } + this.tagInterceptor = + null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor; + this.defaultSpanTags = defaultSpanTags; + this.defaultSpanTagsNeedsIntercept = this.tagInterceptor.needsIntercept(this.defaultSpanTags); this.dynamicConfig = DynamicConfig.create(ConfigSnapshot::new) @@ -719,10 +793,7 @@ private CoreTracer( if (config.isDataStreamsEnabled()) { Propagators.register(DSM_CONCERN, this.dataStreamsMonitoring.propagator()); } - - this.tagInterceptor = - null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor; - + if (config.isCiVisibilityEnabled()) { if (config.isCiVisibilityTraceSanitationEnabled()) { addTraceInterceptor(CiVisibilityTraceInterceptor.INSTANCE); @@ -774,12 +845,14 @@ private CoreTracer( this.flushOnClose = flushOnClose; this.allowInferredServices = SpanNaming.instance().namingSchema().allowInferredServices(); if (profilingContextIntegration != ProfilingContextIntegration.NoOp.INSTANCE) { - Map tmp = new HashMap<>(localRootSpanTags); + TagMap tmp = TagMap.fromMap(localRootSpanTags); tmp.put(PROFILING_CONTEXT_ENGINE, profilingContextIntegration.name()); - this.localRootSpanTags = tryMakeImmutableMap(tmp); + this.localRootSpanTags = tmp.freeze(); } else { - this.localRootSpanTags = localRootSpanTags; + this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); } + + this.localRootSpanTagsNeedIntercept = this.tagInterceptor.needsIntercept(this.localRootSpanTags); } /** Used by AgentTestRunner to inject configuration into the test tracer. */ @@ -1280,7 +1353,7 @@ public class CoreSpanBuilder implements AgentTracer.SpanBuilder { private final CoreTracer tracer; // Builder attributes - private Map tags; + private TagMap.Builder tagBuilder; private long timestampMicro; private AgentSpanContext parent; private String serviceName; @@ -1411,14 +1484,14 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { if (tag == null) { return this; } - Map tagMap = tags; - if (tagMap == null) { - tags = tagMap = new LinkedHashMap<>(); // Insertion order is important + TagMap.Builder tagBuilder = this.tagBuilder; + if (tagBuilder == null) { + this.tagBuilder = tagBuilder = TagMap.builder(); // Insertion order is important } if (value == null) { - tagMap.remove(tag); + tagBuilder.remove(tag); } else { - tagMap.put(tag, value); + tagBuilder.put(tag, value); } return this; } @@ -1470,9 +1543,10 @@ private DDSpanContext buildSpanContext() { final TraceCollector parentTraceCollector; final int samplingPriority; final CharSequence origin; - final Map coreTags; - final Map rootSpanTags; - + final TagMap coreTags; + boolean coreTagsNeedsIntercept; + final TagMap rootSpanTags; + boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; Object requestContextDataAppSec; Object requestContextDataIast; @@ -1510,7 +1584,9 @@ private DDSpanContext buildSpanContext() { samplingPriority = PrioritySampling.UNSET; origin = null; coreTags = null; + coreTagsNeedsIntercept = false; rootSpanTags = null; + rootSpanTagsNeedsIntercept = false; parentServiceName = ddsc.getServiceName(); if (serviceName == null) { serviceName = parentServiceName; @@ -1563,6 +1639,7 @@ private DDSpanContext buildSpanContext() { TagContext tc = (TagContext) parentContext; traceConfig = (ConfigSnapshot) tc.getTraceConfig(); coreTags = tc.getTags(); + coreTagsNeedsIntercept = true; // may intercept isn't needed? origin = tc.getOrigin(); baggage = tc.getBaggage(); requestContextDataAppSec = tc.getRequestContextDataAppSec(); @@ -1571,6 +1648,7 @@ private DDSpanContext buildSpanContext() { } else { traceConfig = null; coreTags = null; + coreTagsNeedsIntercept = false; origin = null; baggage = null; requestContextDataAppSec = null; @@ -1579,6 +1657,7 @@ private DDSpanContext buildSpanContext() { } rootSpanTags = localRootSpanTags; + rootSpanTagsNeedsIntercept = localRootSpanTagsNeedIntercept; parentTraceCollector = createTraceCollector(traceId, traceConfig); @@ -1629,14 +1708,16 @@ private DDSpanContext buildSpanContext() { final CharSequence operationName = this.operationName != null ? this.operationName : resourceName; - final Map mergedTracerTags = traceConfig.mergedTracerTags; + final TagMap mergedTracerTags = traceConfig.mergedTracerTags; + boolean mergedTracerTagsNeedsIntercept = traceConfig.mergedTracerTagsNeedsIntercept; - final int tagsSize = - mergedTracerTags.size() - + (null == tags ? 0 : tags.size()) - + (null == coreTags ? 0 : coreTags.size()) - + (null == rootSpanTags ? 0 : rootSpanTags.size()) - + (null == contextualTags ? 0 : contextualTags.size()); + final int tagsSize = 0; +// final int tagsSize = +// mergedTracerTags.computeSize() +// + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) +// + (null == coreTags ? 0 : coreTags.size()) +// + (null == rootSpanTags ? 0 : rootSpanTags.size()) +// + (null == contextualTags ? 0 : contextualTags.size()); if (builderRequestContextDataAppSec != null) { requestContextDataAppSec = builderRequestContextDataAppSec; @@ -1678,13 +1759,11 @@ private DDSpanContext buildSpanContext() { // By setting the tags on the context we apply decorators to any tags that have been set via // the builder. This is the order that the tags were added previously, but maybe the `tags` // set in the builder should come last, so that they override other tags. - context.setAllTags(mergedTracerTags); - context.setAllTags(tags); - context.setAllTags(coreTags); - context.setAllTags(rootSpanTags); - if (contextualTags != null) { - context.setAllTags(contextualTags); - } + context.setAllTags(mergedTracerTags, mergedTracerTagsNeedsIntercept); + context.setAllTags(tagBuilder); + context.setAllTags(coreTags, coreTagsNeedsIntercept); + context.setAllTags(rootSpanTags, rootSpanTagsNeedsIntercept); + context.setAllTags(contextualTags); return context; } } @@ -1709,12 +1788,13 @@ public void run() { protected class ConfigSnapshot extends DynamicConfig.Snapshot { final Sampler sampler; - final Map mergedTracerTags; + final TagMap mergedTracerTags; + final boolean mergedTracerTagsNeedsIntercept; protected ConfigSnapshot( DynamicConfig.Builder builder, ConfigSnapshot oldSnapshot) { super(builder, oldSnapshot); - + if (null == oldSnapshot) { sampler = CoreTracer.this.initialSampler; } else if (Objects.equals(getTraceSampleRate(), oldSnapshot.getTraceSampleRate()) @@ -1725,11 +1805,14 @@ protected ConfigSnapshot( } if (null == oldSnapshot) { - mergedTracerTags = CoreTracer.this.defaultSpanTags; + mergedTracerTags = CoreTracer.this.defaultSpanTags.immutableCopy(); + this.mergedTracerTagsNeedsIntercept = CoreTracer.this.defaultSpanTagsNeedsIntercept; } else if (getTracingTags().equals(oldSnapshot.getTracingTags())) { mergedTracerTags = oldSnapshot.mergedTracerTags; + mergedTracerTagsNeedsIntercept = oldSnapshot.mergedTracerTagsNeedsIntercept; } else { mergedTracerTags = withTracerTags(getTracingTags(), CoreTracer.this.initialConfig, this); + mergedTracerTagsNeedsIntercept = CoreTracer.this.tagInterceptor.needsIntercept(mergedTracerTags); } } } @@ -1737,9 +1820,9 @@ protected ConfigSnapshot( /** * Tags added by the tracer to all spans; combines user-supplied tags with tracer-defined tags. */ - static Map withTracerTags( + static TagMap withTracerTags( Map userSpanTags, Config config, TraceConfig traceConfig) { - final Map result = new HashMap<>(userSpanTags.size() + 5, 1f); + final TagMap result = new TagMap(); result.putAll(userSpanTags); if (null != config) { // static if (!config.getEnv().isEmpty()) { @@ -1762,6 +1845,6 @@ protected ConfigSnapshot( result.remove(DSM_ENABLED); } } - return Collections.unmodifiableMap(result); + return result.freeze(); } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java index 0b576654936..1e055e29c75 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java @@ -12,6 +12,7 @@ import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; import datadog.trace.api.EndpointTracker; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; @@ -692,7 +693,7 @@ public String getSpanType() { } @Override - public Map getTags() { + public TagMap getTags() { // This is an imutable copy of the tags return context.getTags(); } 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 e6980e2b41d..3349205d444 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 @@ -7,6 +7,8 @@ import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; +import datadog.trace.api.TagMap; + import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; import datadog.trace.api.config.TracerConfig; @@ -94,7 +96,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 Map unsafeTags; + private final TagMap unsafeTags; /** The service name is required, otherwise the span are dropped by the agent */ private volatile String serviceName; @@ -340,10 +342,8 @@ public DDSpanContext( assert pathwayContext != null; this.pathwayContext = pathwayContext; - // 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 = new HashMap<>(capacity); + this.unsafeTags = new TagMap(); + // must set this before setting the service and resource names below this.profilingContextIntegration = profilingContextIntegration; // as fast as we can try to make this operation, we still might need to activate/deactivate @@ -749,22 +749,67 @@ public void setTag(final String tag, final Object value) { } } } - - void setAllTags(final Map map) { - if (map == null || map.isEmpty()) { + + void setAllTags(final TagMap map, boolean needsIntercept) { + if ( map == null ) { return; } - + + synchronized (unsafeTags) { + if ( needsIntercept ) { + map.forEach(this, traceCollector.getTracer().getTagInterceptor(), (ctx, tagInterceptor, tagEntry) -> { + String tag = tagEntry.tag(); + Object value = tagEntry.objectValue(); + + if (!tagInterceptor.interceptTag(ctx, tag, value)) { + ctx.unsafeTags.putEntry(tagEntry); + } + }); + } else { + unsafeTags.putAll(map); + } + } + } + + void setAllTags(final TagMap.Builder builder) { + if ( builder == null ) { + return; + } + TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { - for (final Map.Entry tag : map.entrySet()) { - if (!tagInterceptor.interceptTag(this, tag.getKey(), tag.getValue())) { - unsafeSetTag(tag.getKey(), tag.getValue()); + for (final TagMap.Entry tagEntry : builder) { + if ( tagEntry.isRemoval() ) { + unsafeTags.removeEntry(tagEntry.tag()); + } else { + String tag = tagEntry.tag(); + Object value = tagEntry.objectValue(); + + if (!tagInterceptor.interceptTag(this, tag, value)) { + unsafeTags.putEntry(tagEntry); + } } } } } + void setAllTags(final Map map) { + if ( map == null ) { + return; + } else if ( map instanceof TagMap ) { + setAllTags((TagMap)map); + } else if ( !map.isEmpty() ) { + TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); + synchronized (unsafeTags) { + for (final Map.Entry tag : map.entrySet()) { + if (!tagInterceptor.interceptTag(this, tag.getKey(), tag.getValue())) { + unsafeSetTag(tag.getKey(), tag.getValue()); + } + } + } + } + } + void unsafeSetTag(final String tag, final Object value) { unsafeTags.put(tag, value); } @@ -796,12 +841,14 @@ Object getTag(final String key) { * @return the value associated with the tag */ public Object unsafeGetTag(final String tag) { - return unsafeTags.get(tag); + return unsafeTags.getObject(tag); } - public Map getTags() { + @Deprecated + public TagMap getTags() { synchronized (unsafeTags) { - Map tags = new HashMap<>(unsafeTags); + TagMap tags = unsafeTags.copy(); + tags.put(DDTags.THREAD_ID, threadId); // maintain previously observable type of the thread name :| tags.put(DDTags.THREAD_NAME, threadName.toString()); @@ -816,7 +863,7 @@ public Map getTags() { if (value != null) { tags.put(Tags.HTTP_URL, value.toString()); } - return Collections.unmodifiableMap(tags); + return tags.freeze(); } } @@ -848,11 +895,11 @@ public void processTagsAndBaggage( final MetadataConsumer consumer, int longRunningVersion, List links) { synchronized (unsafeTags) { // Tags - Map tags = - TagsPostProcessorFactory.instance().processTags(unsafeTags, this, links); + TagsPostProcessorFactory.instance().processTags(unsafeTags, this, links); + String linksTag = DDSpanLink.toTag(links); if (linksTag != null) { - tags.put(SPAN_LINKS, linksTag); + unsafeTags.put(SPAN_LINKS, linksTag); } // Baggage Map baggageItemsWithPropagationTags; @@ -867,7 +914,7 @@ public void processTagsAndBaggage( new Metadata( threadId, threadName, - tags, + unsafeTags, baggageItemsWithPropagationTags, samplingPriority != PrioritySampling.UNSET ? samplingPriority : getSamplingPriority(), measured, 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 f1f79454164..08a1f46ca9f 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,14 +2,17 @@ import static datadog.trace.api.sampling.PrioritySampling.UNSET; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import java.util.HashMap; import java.util.Map; +import datadog.trace.api.TagMap; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; + public final class Metadata { private final long threadId; private final UTF8BytesString threadName; private final UTF8BytesString httpStatusCode; - private final Map tags; + private final TagMap tags; private final Map baggage; private final int samplingPriority; @@ -21,7 +24,7 @@ public final class Metadata { public Metadata( long threadId, UTF8BytesString threadName, - Map tags, + TagMap tags, Map baggage, int samplingPriority, boolean measured, @@ -57,8 +60,8 @@ public UTF8BytesString getThreadName() { return threadName; } - public Map getTags() { - return tags; + public TagMap getTags() { + return this.tags; } public Map getBaggage() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java index 910449b7dd1..f13cabf2ec6 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java @@ -186,10 +186,7 @@ public B3BaseContextInterpreter(Config config) { protected void setSpanId(final String sId) { spanId = DDSpanId.fromHex(sId); - if (tags.isEmpty()) { - tags = new TreeMap<>(); - } - tags.put(B3_SPAN_ID, sId); + tagBuilder().put(B3_SPAN_ID, sId); } protected boolean setTraceId(final String tId) { @@ -202,10 +199,7 @@ protected boolean setTraceId(final String tId) { B3TraceId b3TraceId = B3TraceId.fromHex(tId); traceId = b3TraceId.toLong() == 0 ? DDTraceId.ZERO : b3TraceId; } - if (tags.isEmpty()) { - tags = new TreeMap<>(); - } - tags.put(B3_TRACE_ID, tId); + tagBuilder().put(B3_TRACE_ID, tId); return true; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java index d3466f76d8b..b8e693a57f9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java @@ -15,10 +15,15 @@ import static datadog.trace.core.propagation.HttpCodec.X_FORWARDED_PROTO_KEY; import static datadog.trace.core.propagation.HttpCodec.X_REAL_IP_KEY; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.cache.DDCache; @@ -27,9 +32,6 @@ import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.TagContext; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; /** * When adding new context fields to the ContextInterpreter class remember to clear them in the @@ -44,7 +46,7 @@ public abstract class ContextInterpreter implements AgentPropagation.KeyClassifi protected DDTraceId traceId; protected long spanId; protected int samplingPriority; - protected Map tags; + protected TagMap.Builder tagBuilder; protected Map baggage; protected CharSequence lastParentId; @@ -76,6 +78,13 @@ protected ContextInterpreter(Config config) { this.propagationTagsFactory = PropagationTags.factory(config); this.requestHeaderTagsCommaAllowed = config.isRequestHeaderTagsCommaAllowed(); } + + final TagMap.Builder tagBuilder() { + if ( tagBuilder == null ) { + tagBuilder = TagMap.builder(); + } + return tagBuilder; + } /** * Gets the propagation style handled by the context interpreter. @@ -189,10 +198,7 @@ protected final boolean handleTags(String key, String value) { final String lowerCaseKey = toLowerCase(key); final String mappedKey = headerTags.get(lowerCaseKey); if (null != mappedKey) { - if (tags.isEmpty()) { - tags = new TreeMap<>(); - } - tags.put( + tagBuilder().put( mappedKey, HttpCodec.decode( requestHeaderTagsCommaAllowed ? value : HttpCodec.firstHeaderValue(value))); @@ -224,7 +230,7 @@ public ContextInterpreter reset(TraceConfig traceConfig) { samplingPriority = PrioritySampling.UNSET; origin = null; endToEndStartTime = 0; - tags = Collections.emptyMap(); + if ( tagBuilder != null ) tagBuilder.reset(); baggage = Collections.emptyMap(); valid = true; fullContext = true; @@ -252,19 +258,19 @@ protected TagContext build() { origin, endToEndStartTime, baggage, - tags, + tagBuilder == null ? null : tagBuilder.build(), httpHeaders, propagationTags, traceConfig, style()); } else if (origin != null - || !tags.isEmpty() + || (tagBuilder != null && !tagBuilder.isDefinitelyEmpty()) || httpHeaders != null || !baggage.isEmpty() || samplingPriority != PrioritySampling.UNSET) { return new TagContext( origin, - tags, + tagBuilder == null ? null : tagBuilder.build(), httpHeaders, baggage, samplingPriorityOrDefault(traceId, samplingPriority), diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java index e799688401d..07e594adc47 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java @@ -1,6 +1,7 @@ package datadog.trace.core.propagation; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.sampling.PrioritySampling; @@ -44,7 +45,7 @@ public ExtractedContext( final CharSequence origin, final long endToEndStartTime, final Map baggage, - final Map tags, + final TagMap tags, final HttpHeaders httpHeaders, final PropagationTags propagationTags, final TraceConfig traceConfig, @@ -63,6 +64,25 @@ public ExtractedContext( this.endToEndStartTime = endToEndStartTime; this.propagationTags = propagationTags; } + + /* + * DQH - kept for testing purposes only + */ + @Deprecated + public ExtractedContext( + final DDTraceId traceId, + final long spanId, + final int samplingPriority, + final CharSequence origin, + final long endToEndStartTime, + final Map baggage, + final Map tags, + final HttpHeaders httpHeaders, + final PropagationTags propagationTags, + final TraceConfig traceConfig, + final TracePropagationStyle propagationStyle) { + this(traceId, spanId, samplingPriority, origin, endToEndStartTime, baggage, tags == null ? null : TagMap.fromMap(tags), httpHeaders, propagationTags, traceConfig, propagationStyle); + } @Override public final DDTraceId getTraceId() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index 931eca80721..b0f10a5f3bc 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -22,6 +22,7 @@ import datadog.trace.api.ConfigDefaults; import datadog.trace.api.DDTags; import datadog.trace.api.Pair; +import datadog.trace.api.TagMap; import datadog.trace.api.config.GeneralConfig; import datadog.trace.api.env.CapturedEnvironment; import datadog.trace.api.normalize.HttpResourceNames; @@ -35,6 +36,7 @@ import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.core.DDSpanContext; import java.net.URI; +import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -81,6 +83,49 @@ public TagInterceptor( shouldSetUrlResourceAsName = ruleFlags.isEnabled(URL_AS_RESOURCE_NAME); this.jeeSplitByDeployment = jeeSplitByDeployment; } + + public boolean needsIntercept(TagMap map) { + for ( TagMap.Entry entry: map ) { + if ( needsIntercept(entry.tag()) ) return true; + } + return false; + } + + public boolean needsIntercept(Map map) { + for ( String tag: map.keySet() ) { + if ( needsIntercept(tag) ) return true; + } + return false; + } + + public boolean needsIntercept(String tag) { + switch (tag) { + case DDTags.RESOURCE_NAME: + case Tags.DB_STATEMENT: + case DDTags.SERVICE_NAME: + case "service": + case Tags.PEER_SERVICE: + case DDTags.MANUAL_KEEP: + case DDTags.MANUAL_DROP: + case Tags.ASM_KEEP: + case Tags.SAMPLING_PRIORITY: + case Tags.PROPAGATED_TRACE_SOURCE: + case Tags.PROPAGATED_DEBUG: + case InstrumentationTags.SERVLET_CONTEXT: + case SPAN_TYPE: + case ANALYTICS_SAMPLE_RATE: + case Tags.ERROR: + case HTTP_STATUS: + case HTTP_METHOD: + case HTTP_URL: + case ORIGIN_KEY: + case MEASURED: + return true; + + default: + return splitServiceTags.contains(tag); + } + } public boolean interceptTag(DDSpanContext span, String tag, Object value) { switch (tag) { 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 c855262f048..5d43edd8046 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,15 @@ 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 class BaseServiceAdder implements TagsPostProcessor { +public final class BaseServiceAdder extends TagsPostProcessor { private final UTF8BytesString ddService; public BaseServiceAdder(@Nullable final String ddService) { @@ -16,14 +17,13 @@ public BaseServiceAdder(@Nullable final String ddService) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap 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/PayloadTagsProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java index d50c8510295..3a63ae7d390 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 @@ -2,6 +2,7 @@ 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; @@ -21,7 +22,7 @@ import org.slf4j.LoggerFactory; /** Post-processor that extracts tags from payload data injected as tags by instrumentations. */ -public final class PayloadTagsProcessor implements TagsPostProcessor { +public final class PayloadTagsProcessor extends TagsPostProcessor { private static final Logger log = LoggerFactory.getLogger(PayloadTagsProcessor.class); private static final String REDACTED = "redacted"; @@ -69,21 +70,22 @@ public static PayloadTagsProcessor create(Config config) { } @Override - public Map processTags( - Map spanTags, DDSpanContext spanContext, List spanLinks) { - int spanMaxTags = maxTags + spanTags.size(); + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + int spanMaxTags = maxTags + unsafeTags.computeSize(); for (Map.Entry tagPrefixRedactionRules : redactionRulesByTagPrefix.entrySet()) { String tagPrefix = tagPrefixRedactionRules.getKey(); RedactionRules redactionRules = tagPrefixRedactionRules.getValue(); - Object tagValue = spanTags.get(tagPrefix); + Object tagValue = unsafeTags.getObject(tagPrefix); if (tagValue instanceof PayloadTagsData) { - if (spanTags.remove(tagPrefix) != null) { + if (unsafeTags.remove(tagPrefix) != null) { spanMaxTags -= 1; } + PayloadTagsData payloadTagsData = (PayloadTagsData) tagValue; PayloadTagsCollector payloadTagsCollector = - new PayloadTagsCollector(maxDepth, spanMaxTags, redactionRules, tagPrefix, spanTags); + new PayloadTagsCollector(maxDepth, spanMaxTags, redactionRules, tagPrefix, unsafeTags); collectPayloadTags(payloadTagsData, payloadTagsCollector); } else if (tagValue != null) { log.debug( @@ -93,7 +95,6 @@ public Map processTags( tagValue); } } - return spanTags; } private void collectPayloadTags( @@ -187,14 +188,14 @@ private static final class PayloadTagsCollector implements JsonStreamParser.Visi private final RedactionRules redactionRules; private final String tagPrefix; - private final Map collectedTags; + private final TagMap collectedTags; public PayloadTagsCollector( int maxDepth, int maxTags, RedactionRules redactionRules, String tagPrefix, - Map collectedTags) { + TagMap collectedTags) { this.maxDepth = maxDepth; this.maxTags = maxTags; this.redactionRules = redactionRules; @@ -259,7 +260,7 @@ public boolean keepParsing(PathCursor path) { } public boolean keepCollectingTags() { - if (collectedTags.size() < maxTags) { + if (collectedTags.computeSize() < maxTags) { return true; } collectedTags.put(DD_PAYLOAD_TAGS_INCOMPLETE, true); 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 625fae0f839..5f979ec8f04 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,16 +2,18 @@ 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; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.DDSpanContext; + import java.util.List; import java.util.Map; import javax.annotation.Nonnull; -public class PeerServiceCalculator implements TagsPostProcessor { +public final class PeerServiceCalculator extends TagsPostProcessor { private final NamingSchema.ForPeerService peerServiceNaming; private final Map peerServiceMapping; @@ -32,25 +34,26 @@ public PeerServiceCalculator() { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - Object peerService = unsafeTags.get(Tags.PEER_SERVICE); + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Object peerService = unsafeTags.getObject(Tags.PEER_SERVICE); // the user set it if (peerService != null) { if (canRemap) { - return remapPeerService(unsafeTags, peerService); + remapPeerService(unsafeTags, peerService); + return; } } else if (peerServiceNaming.supports()) { // calculate the defaults (if any) peerServiceNaming.tags(unsafeTags); // only remap if the mapping is not empty (saves one get) - return remapPeerService(unsafeTags, canRemap ? unsafeTags.get(Tags.PEER_SERVICE) : null); + remapPeerService(unsafeTags, canRemap ? unsafeTags.getObject(Tags.PEER_SERVICE) : null); + return; } // we have no peer.service and we do not compute defaults. Leave the map untouched - return unsafeTags; } - private Map remapPeerService(Map unsafeTags, Object value) { + private void remapPeerService(TagMap unsafeTags, Object value) { if (value != null) { String mapped = peerServiceMapping.get(value); if (mapped != null) { @@ -58,6 +61,5 @@ private Map remapPeerService(Map unsafeTags, Obj unsafeTags.put(DDTags.PEER_SERVICE_REMAPPED_FROM, value); } } - return unsafeTags; } } 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 fbf5b511e24..5bef7746540 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,15 @@ 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 class PostProcessorChain implements TagsPostProcessor { +public final class PostProcessorChain extends TagsPostProcessor { private final TagsPostProcessor[] chain; public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { @@ -15,12 +17,9 @@ public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - Map currentTags = unsafeTags; + public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { for (final TagsPostProcessor tagsPostProcessor : chain) { - currentTags = tagsPostProcessor.processTags(currentTags, spanContext, spanLinks); + tagsPostProcessor.processTags(unsafeTags, spanContext, spanLinks); } - return currentTags; } } 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 934aa5df1f0..19fa70cc52f 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,6 +4,7 @@ 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; @@ -13,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class QueryObfuscator implements TagsPostProcessor { +public final class QueryObfuscator extends TagsPostProcessor { private static final Logger log = LoggerFactory.getLogger(QueryObfuscator.class); @@ -58,20 +59,17 @@ private String obfuscate(String query) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - Object query = unsafeTags.get(DDTags.HTTP_QUERY); + public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Object query = unsafeTags.getObject(DDTags.HTTP_QUERY); if (query instanceof CharSequence) { query = obfuscate(query.toString()); unsafeTags.put(DDTags.HTTP_QUERY, query); - Object url = unsafeTags.get(Tags.HTTP_URL); + Object url = unsafeTags.getObject(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 b872b824e38..953e8a67387 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,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.core.DDSpanContext; + import java.util.List; -import java.util.Map; import java.util.function.Supplier; -public class RemoteHostnameAdder implements TagsPostProcessor { +public final class RemoteHostnameAdder extends TagsPostProcessor { private final Supplier hostnameSupplier; public RemoteHostnameAdder(Supplier hostnameSupplier) { @@ -15,11 +16,10 @@ public RemoteHostnameAdder(Supplier hostnameSupplier) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap 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 fabf01fc7a4..a8c9da98da8 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 @@ -6,6 +6,7 @@ import static datadog.trace.bootstrap.instrumentation.api.InstrumentationTags.AWS_OBJECT_KEY; 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; @@ -20,7 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SpanPointersProcessor implements TagsPostProcessor { +public final class SpanPointersProcessor extends TagsPostProcessor { private static final Logger LOG = LoggerFactory.getLogger(SpanPointersProcessor.class); // The pointer direction will always be down. The serverless agent handles cases where the @@ -30,18 +31,20 @@ public class SpanPointersProcessor implements TagsPostProcessor { static final String LINK_KIND = "span-pointer"; @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - String eTag = asString(unsafeTags.remove(S3_ETAG)); - if (eTag == null) { - return unsafeTags; + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + TagMap.Entry eTagEntry = unsafeTags.removeEntry(S3_ETAG); + if (eTagEntry == null) { + return; } - String bucket = asString(unsafeTags.get(AWS_BUCKET_NAME)); - String key = asString(unsafeTags.get(AWS_OBJECT_KEY)); + String bucket = unsafeTags.getString(AWS_BUCKET_NAME); + String key = unsafeTags.getString(AWS_OBJECT_KEY); if (bucket == null || key == null) { LOG.debug("Unable to calculate span pointer hash because could not find bucket or key tags."); - return unsafeTags; + return; } + + String eTag = eTagEntry.stringValue(); // Hash calculation rules: // https://github.com/DataDog/dd-span-pointer-rules/blob/main/AWS/S3/Object/README.md @@ -63,8 +66,6 @@ public Map processTags( } catch (Exception e) { LOG.debug("Failed to add span pointer: {}", e.getMessage()); } - - return unsafeTags; } private static String asString(Object o) { 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 f188e10d090..4050e9baf30 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,11 +1,27 @@ 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 interface TagsPostProcessor { - Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks); +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); } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy index db3bd899805..168fa2ce24d 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy @@ -4,6 +4,7 @@ import datadog.trace.api.DDSpanId import datadog.trace.api.DDTags import datadog.trace.api.DDTraceId import datadog.trace.api.IdGenerationStrategy +import datadog.trace.api.TagMap import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString import datadog.trace.core.CoreSpan @@ -176,7 +177,7 @@ class TraceGenerator { this.measured = measured this.samplingPriority = samplingPriority this.metadata = new Metadata(Thread.currentThread().getId(), - UTF8BytesString.create(Thread.currentThread().getName()), tags, baggage, samplingPriority, measured, topLevel, + UTF8BytesString.create(Thread.currentThread().getName()), TagMap.fromMap(tags), baggage, samplingPriority, measured, topLevel, statusCode == 0 ? null : UTF8BytesString.create(Integer.toString(statusCode)), origin, 0) this.httpStatusCode = (short) statusCode } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy index 28ceda7c1c4..9ab169bb5d0 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy @@ -8,6 +8,7 @@ import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY import datadog.trace.api.Config import datadog.trace.api.DDSpanId import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.naming.SpanNaming import datadog.trace.api.sampling.PrioritySampling @@ -361,9 +362,9 @@ class CoreSpanBuilderTest extends DDCoreSpecification { ] + productTags() where: - tagContext | _ - new TagContext(null, [:]) | _ - new TagContext("some-origin", ["asdf": "qwer"]) | _ + tagContext | _ + new TagContext(null, TagMap.fromMap([:])) | _ + new TagContext("some-origin", TagMap.fromMap(["asdf": "qwer"])) | _ } def "global span tags populated on each span"() { diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy index a66042c50d9..54edfd095df 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy @@ -3,6 +3,7 @@ package datadog.trace.core import datadog.trace.api.DDSpanId import datadog.trace.api.DDTags import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext @@ -274,7 +275,7 @@ class DDSpanTest extends DDCoreSpecification { where: extractedContext | _ - new TagContext("some-origin", [:]) | _ + new TagContext("some-origin", TagMap.fromMap([:])) | _ new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, "some-origin", propagationTagsFactory.empty(), DATADOG) | _ } 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 2a1dc2583f3..cd82cc1a2a6 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 @@ -1,5 +1,6 @@ package datadog.trace.core.tagprocessor +import datadog.trace.api.TagMap import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink import datadog.trace.core.DDSpanContext import datadog.trace.test.util.DDSpecification @@ -9,55 +10,53 @@ class PostProcessorChainTest extends DDSpecification { setup: def processor1 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { unsafeTags.put("key1", "processor1") unsafeTags.put("key2", "processor1") - return unsafeTags } } def processor2 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { unsafeTags.put("key1", "processor2") - return unsafeTags } } def chain = new PostProcessorChain(processor1, processor2) - def tags = ["key1": "root", "key3": "root"] + def tags = TagMap.fromMap(["key1": "root", "key3": "root"]) when: - def out = chain.processTags(tags, null, []) + chain.processTags(tags, null, []) then: - assert out == ["key1": "processor2", "key2": "processor1", "key3": "root"] + assert tags == ["key1": "processor2", "key2": "processor1", "key3": "root"] } def "processor can hide tags to next one()"() { setup: def processor1 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - return ["my": "tag"] + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + unsafeTags.clear() + unsafeTags.put("my", "tag") } } def processor2 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { if (unsafeTags.containsKey("test")) { unsafeTags.put("found", "true") - } - return unsafeTags + } } } def chain = new PostProcessorChain(processor1, processor2) - def tags = ["test": "test"] + def tags = TagMap.fromMap(["test": "test"]) when: - def out = chain.processTags(tags, null, []) + chain.processTags(tags, null, []) then: - assert out == ["my": "tag"] + assert tags == ["my": "tag"] } } diff --git a/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy b/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy index 9089f8b5a43..c796ff3f89a 100644 --- a/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy +++ b/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy @@ -2,6 +2,7 @@ import datadog.trace.api.DDSpanId import datadog.trace.api.DDTags import datadog.trace.api.DDTraceId import datadog.trace.api.IdGenerationStrategy +import datadog.trace.api.TagMap import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString import datadog.trace.core.CoreSpan import datadog.trace.core.Metadata @@ -155,7 +156,7 @@ class TraceGenerator { this.type = type this.measured = measured this.metadata = new Metadata(Thread.currentThread().getId(), - UTF8BytesString.create(Thread.currentThread().getName()), tags, baggage, UNSET, measured, topLevel, null, null, 0) + UTF8BytesString.create(Thread.currentThread().getName()), TagMap.fromMap(tags), baggage, UNSET, measured, topLevel, null, null, 0) } @Override @@ -297,7 +298,7 @@ class TraceGenerator { return metadata.getBaggage() } - Map getTags() { + TagMap getTags() { return metadata.getTags() } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 004a609898e..53029db8f9e 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3467,10 +3467,12 @@ public boolean isApmTracingEnabled() { } /** @return A map of tags to be applied only to the local application root span. */ - public Map getLocalRootSpanTags() { + public TagMap getLocalRootSpanTags() { final Map runtimeTags = getRuntimeTags(); - final Map result = new HashMap<>(runtimeTags.size() + 2); + + final TagMap result = new TagMap(); result.putAll(runtimeTags); + result.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); result.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); result.put(DDTags.PROFILING_ENABLED, isProfilingEnabled() ? 1 : 0); @@ -3491,7 +3493,7 @@ public Map getLocalRootSpanTags() { result.putAll(getProcessIdTag()); - return Collections.unmodifiableMap(result); + return result.freeze(); } public WellKnownTags getWellKnownTags() { diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java new file mode 100644 index 00000000000..5b7b0b45934 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -0,0 +1,2035 @@ +package datadog.trace.api; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import datadog.trace.api.function.TriConsumer; + +/** + * A super simple hash map designed for... + * - fast copy from one map to another + * - compatibility with Builder idioms + * - building small maps as fast as possible + * - storing primitives without boxing + * - minimal memory footprint + * + * This is mainly accomplished by using immutable entry objects that can + * reference an object or a primitive. By using immutable entries, + * the entry objects can be shared between builders & maps freely. + * + * This map lacks some features of a regular java.util.Map... + * - Entry object mutation + * - size tracking - falls back to computeSize + * - manipulating Map through the entrySet() or values() + * + * Also lacks features designed for handling large maps... + * - bucket array expansion + * - adaptive collision + */ +public final class TagMap implements Map, Iterable { + public static final TagMap EMPTY = createEmpty(); + + static final TagMap createEmpty() { + return new TagMap().freeze(); + } + + public static final Builder builder() { + return new Builder(); + } + + public static final Builder builder(int size) { + return new Builder(size); + } + + public static final TagMap fromMap(Map map) { + TagMap tagMap = new TagMap(); + tagMap.putAll(map); + return tagMap; + } + + public static final TagMap fromMapImmutable(Map map) { + if ( map.isEmpty() ) { + return TagMap.EMPTY; + } else { + return fromMap(map).freeze(); + } + } + + private final Object[] buckets; + private boolean frozen; + + public TagMap() { + // needs to be a power of 2 for bucket masking calculation to work as intended + this.buckets = new Object[1 << 4]; + this.frozen = false; + } + + /** + * Used for inexpensive immutable + */ + private TagMap(Object[] buckets) { + this.buckets = buckets; + this.frozen = true; + } + + @Deprecated + @Override + public final int size() { + return this.computeSize(); + } + + /** + * Computes the size of the TagMap + * + * computeSize is fast but is an O(n) operation. + */ + public final int computeSize() { + Object[] thisBuckets = this.buckets; + + int size = 0; + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object curBucket = thisBuckets[i]; + + if ( curBucket instanceof Entry ) { + size += 1; + } else if ( curBucket instanceof BucketGroup ) { + BucketGroup curGroup = (BucketGroup)curBucket; + size += curGroup.sizeInChain(); + } + } + return size; + } + + @Deprecated + @Override + public boolean isEmpty() { + return this.checkIfEmpty(); + } + + public final boolean checkIfEmpty() { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object curBucket = thisBuckets[i]; + + if ( curBucket instanceof Entry ) { + return false; + } else if ( curBucket instanceof BucketGroup ) { + BucketGroup curGroup = (BucketGroup)curBucket; + if ( !curGroup.isEmptyChain() ) return false; + } + } + + return true; + } + + @Override + public boolean containsKey(Object key) { + if ( !(key instanceof String) ) return false; + + return (this.getEntry((String)key) != null); + } + + @Override + public boolean containsValue(Object value) { + for ( Entry entry : this ) { + if ( entry.objectValue().equals(value) ) return true; + } + return false; + } + + @Deprecated + @Override + public Set keySet() { + return new Keys(this); + } + + @Deprecated + @Override + public Collection values() { + return new Values(this); + } + + @Deprecated + @Override + public Set> entrySet() { + return new Entries(this); + } + + @Deprecated + @Override + public final Object get(Object tag) { + if ( !(tag instanceof String) ) return null; + + return this.getObject((String)tag); + } + + public final Object getObject(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? null : entry.objectValue(); + } + + public final String getString(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? null : entry.stringValue(); + } + + public final boolean getBoolean(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? false : entry.booleanValue(); + } + + public final int getInt(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0 : entry.intValue(); + } + + public final long getLong(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0L : entry.longValue(); + } + + public final float getFloat(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0F : entry.floatValue(); + } + + public final double getDouble(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0D : entry.doubleValue(); + } + + public final Entry getEntry(String tag) { + Object[] thisBuckets = this.buckets; + + int hash = _hash(tag); + int bucketIndex = hash & (thisBuckets.length - 1); + + Object bucket = thisBuckets[bucketIndex]; + if ( bucket == null ) { + return null; + } else if ( bucket instanceof Entry ) { + Entry tagEntry = (Entry)bucket; + if ( tagEntry.matches(tag) ) return tagEntry; + } else if ( bucket instanceof BucketGroup ) { + BucketGroup lastGroup = (BucketGroup)bucket; + + Entry tagEntry = lastGroup.findInChain(hash, tag); + if ( tagEntry != null ) return tagEntry; + } + return null; + } + + @Deprecated + public final Object put(String tag, Object value) { + TagMap.Entry entry = this.putEntry(Entry.newAnyEntry(tag, value)); + return entry == null ? null : entry.objectValue(); + } + + public final Entry set(String tag, Object value) { + return this.putEntry(Entry.newAnyEntry(tag, value)); + } + + public final Entry set(String tag, CharSequence value) { + return this.putEntry(Entry.newObjectEntry(tag, value)); + } + + public final Entry set(String tag, boolean value) { + return this.putEntry(Entry.newBooleanEntry(tag, value)); + } + + public final Entry set(String tag, int value) { + return this.putEntry(Entry.newIntEntry(tag, value)); + } + + public final Entry set(String tag, long value) { + return this.putEntry(Entry.newLongEntry(tag, value)); + } + + public final Entry set(String tag, float value) { + return this.putEntry(Entry.newFloatEntry(tag, value)); + } + + public final Entry set(String tag, double value) { + return this.putEntry(Entry.newDoubleEntry(tag, value)); + } + + public final Entry putEntry(Entry newEntry) { + this.checkWriteAccess(); + + Object[] thisBuckets = this.buckets; + + int newHash = newEntry.hash(); + int bucketIndex = newHash & (thisBuckets.length - 1); + + Object bucket = thisBuckets[bucketIndex]; + if ( bucket == null ) { + thisBuckets[bucketIndex] = newEntry; + + return null; + } else if ( bucket instanceof Entry ) { + Entry existingEntry = (Entry)bucket; + if ( existingEntry.matches(newEntry.tag) ) { + thisBuckets[bucketIndex] = newEntry; + + return existingEntry; + } else { + thisBuckets[bucketIndex] = new BucketGroup( + existingEntry.hash(), existingEntry, + newHash, newEntry); + + return null; + } + } else if ( bucket instanceof BucketGroup ){ + BucketGroup lastGroup = (BucketGroup)bucket; + + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag); + if ( containingGroup != null ) { + return containingGroup._replace(newHash, newEntry); + } + + if ( !lastGroup.insertInChain(newHash, newEntry) ) { + thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup); + } + return null; + } + return null; + } + + public final void putAll(Iterable entries) { + this.checkWriteAccess(); + + for ( Entry tagEntry: entries ) { + if ( tagEntry.isRemoval() ) { + this.remove(tagEntry.tag); + } else { + this.putEntry(tagEntry); + } + } + } + + public final void putAll(TagMap.Builder builder) { + putAll(builder.entries, builder.nextPos); + } + + private final void putAll(Entry[] tagEntries, int size) { + for ( int i = 0; i < size && i < tagEntries.length; ++i ) { + Entry tagEntry = tagEntries[i]; + + if ( tagEntry.isRemoval() ) { + this.remove(tagEntry.tag); + } else { + this.putEntry(tagEntry); + } + } + } + + public final void putAll(TagMap that) { + this.checkWriteAccess(); + + Object[] thisBuckets = this.buckets; + Object[] thatBuckets = that.buckets; + + // Since TagMap-s don't support expansion, buckets are perfectly aligned + for ( int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i ) { + Object thatBucket = thatBuckets[i]; + + // nothing in the other hash, just skip this bucket + if ( thatBucket == null ) continue; + + Object thisBucket = thisBuckets[i]; + + if ( thisBucket == null ) { + // This bucket is null, easy case + // Either copy over the sole entry or clone the BucketGroup chain + + if ( thatBucket instanceof Entry ) { + thisBuckets[i] = thatBucket; + } else if ( thatBucket instanceof BucketGroup ) { + BucketGroup thatGroup = (BucketGroup)thatBucket; + + thisBuckets[i] = thatGroup.cloneChain(); + } + } else if ( thisBucket instanceof Entry ) { + // This bucket is a single entry, medium complexity case + // If other side is an Entry - just merge the entries into a bucket + // If other side is a BucketGroup - then clone the group and insert the entry normally into the cloned group + + Entry thisEntry = (Entry)thisBucket; + int thisHash = thisEntry.hash(); + + if ( thatBucket instanceof Entry ) { + Entry thatEntry = (Entry)thatBucket; + int thatHash = thatEntry.hash(); + + if ( thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { + thisBuckets[i] = thatEntry; + } else { + thisBuckets[i] = new BucketGroup( + thisHash, thisEntry, + thatHash, thatEntry); + } + } else if ( thatBucket instanceof BucketGroup ) { + BucketGroup thatGroup = (BucketGroup)thatBucket; + + // Clone the other group, then place this entry into that group + BucketGroup thisNewGroup = thatGroup.cloneChain(); + + Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag()); + if ( incomingEntry != null ) { + // there's already an entry w/ the same tag from the incoming TagMap - done + thisBuckets[i] = thisNewGroup; + } else if ( thisNewGroup.insertInChain(thisHash, thisEntry) ) { + // able to add thisEntry into the existing groups + thisBuckets[i] = thisNewGroup; + } else { + // unable to add into the existing groups + thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); + } + } + } else if ( thisBucket instanceof BucketGroup ) { + // This bucket is a BucketGroup, medium to hard case + // If the other side is an entry, just normal insertion procedure - no cloning required + BucketGroup thisGroup = (BucketGroup)thisBucket; + + if ( thatBucket instanceof Entry ) { + Entry thatEntry = (Entry)thatBucket; + int thatHash = thatEntry.hash(); + + if ( !thisGroup.replaceOrInsertInChain(thatHash, thatEntry) ) { + thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup); + } + } else if ( thatBucket instanceof BucketGroup ) { + // Most complicated case - need to walk that bucket group chain and update this chain + BucketGroup thatGroup = (BucketGroup)thatBucket; + + thisBuckets[i] = thisGroup.replaceOrInsertAllInChain(thatGroup); + } + } + } + } + + public final void putAll(Map map) { + this.checkWriteAccess(); + + if ( map instanceof TagMap ) { + this.putAll((TagMap)map); + } else { + for ( Map.Entry entry: map.entrySet() ) { + this.put(entry.getKey(), entry.getValue()); + } + } + } + + public final void fillMap(Map map) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + map.put(thisEntry.tag, thisEntry.objectValue()); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.fillMapFromChain(map); + } + } + } + + public final void fillStringMap(Map stringMap) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + stringMap.put(thisEntry.tag, thisEntry.stringValue()); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.fillStringMapFromChain(stringMap); + } + } + } + + @Deprecated + @Override + public final Object remove(Object tag) { + if ( !(tag instanceof String) ) return null; + + Entry entry = this.removeEntry((String)tag); + return entry == null ? null : entry.objectValue(); + } + + public final Entry removeEntry(String tag) { + this.checkWriteAccess(); + + Object[] thisBuckets = this.buckets; + + int hash = _hash(tag); + int bucketIndex = hash & (thisBuckets.length - 1); + + Object bucket = thisBuckets[bucketIndex]; + // null bucket case - do nothing + if ( bucket instanceof Entry ) { + Entry existingEntry = (Entry)bucket; + if (existingEntry.matches(tag)) { + thisBuckets[bucketIndex] = null; + return existingEntry; + } else { + return null; + } + } else if ( bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup)bucket; + + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag); + if ( containingGroup == null ) { + return null; + } + + Entry existingEntry = containingGroup._remove(hash, tag); + if ( containingGroup._isEmpty() ) { + this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup); + } + + return existingEntry; + } + return null; + } + + public final TagMap copy() { + TagMap copy = new TagMap(); + copy.putAll(this); + return copy; + } + + public final TagMap immutableCopy() { + if ( this.frozen ) { + return this; + } else { + return this.copy().freeze(); + } + } + + public final TagMap immutable() { + // specialized constructor, freezes map immediately + return new TagMap(this.buckets); + } + + @Override + public final Iterator iterator() { + return new EntryIterator(this); + } + + public final Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + public final void forEach(Consumer consumer) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + consumer.accept(thisEntry); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.forEachInChain(consumer); + } + } + } + + public final void forEach(T thisObj, BiConsumer consumer) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + consumer.accept(thisObj, thisEntry); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.forEachInChain(thisObj, consumer); + } + } + } + + public final void forEach(T thisObj, U otherObj, TriConsumer consumer) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + consumer.accept(thisObj, otherObj, thisEntry); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.forEachInChain(thisObj, otherObj, consumer); + } + } + } + + public final void clear() { + this.checkWriteAccess(); + + Arrays.fill(this.buckets, null); + } + + public final TagMap freeze() { + this.frozen = true; + + return this; + } + + public boolean isFrozen() { + return this.frozen; + } + +// final void check() { +// Object[] thisBuckets = this.buckets; +// +// for ( int i = 0; i < thisBuckets.length; ++i ) { +// Object thisBucket = thisBuckets[i]; +// +// if ( thisBucket instanceof Entry ) { +// Entry thisEntry = (Entry)thisBucket; +// int thisHash = thisEntry.hash(); +// +// int expectedBucket = thisHash & (thisBuckets.length - 1); +// assert expectedBucket == i; +// } else if ( thisBucket instanceof BucketGroup ) { +// BucketGroup thisGroup = (BucketGroup)thisBucket; +// +// for ( BucketGroup curGroup = thisGroup; +// curGroup != null; +// curGroup = curGroup.prev ) +// { +// for ( int j = 0; j < BucketGroup.LEN; ++j ) { +// Entry thisEntry = curGroup._entryAt(i); +// if ( thisEntry == null ) continue; +// +// int thisHash = thisEntry.hash(); +// assert curGroup._hashAt(i) == thisHash; +// +// int expectedBucket = thisHash & (thisBuckets.length - 1); +// assert expectedBucket == i; +// } +// } +// } +// } +// } + + @Override + public String toString() { + return toInternalString(); + } + + String toPrettyString() { + boolean first = true; + + StringBuilder builder = new StringBuilder(128); + builder.append('{'); + for ( Entry entry: this ) { + if ( first ) { + first = false; + } else { + builder.append(","); + } + + builder.append(entry.tag).append('=').append(entry.stringValue()); + } + builder.append('}'); + return builder.toString(); + } + + String toInternalString() { + Object[] thisBuckets = this.buckets; + + StringBuilder builder = new StringBuilder(128); + for ( int i = 0; i < thisBuckets.length; ++i ) { + builder.append('[').append(i).append("] = "); + + Object thisBucket = thisBuckets[i]; + if ( thisBucket == null ) { + builder.append("null"); + } else if ( thisBucket instanceof Entry ) { + builder.append('{').append(thisBucket).append('}'); + } else if ( thisBucket instanceof BucketGroup ) { + for ( BucketGroup curGroup = (BucketGroup)thisBucket; + curGroup != null; + curGroup = curGroup.prev ) + { + builder.append(curGroup).append(" -> "); + } + } + builder.append('\n'); + } + return builder.toString(); + } + + public final void checkWriteAccess() { + if ( this.frozen ) throw new IllegalStateException("TagMap frozen"); + } + + static final int _hash(String tag) { + int hash = tag.hashCode(); + return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); + } + + public static final class Entry implements Map.Entry { + /* + * Special value used to record removals in the builder + * Removal entries are never stored in the TagMap itself + */ + private static final byte REMOVED = -1; + + /* + * Special value used for Objects that haven't been type checked yet. + * These objects might be primitive box objects. + */ + public static final byte ANY = 0; + public static final byte OBJECT = 1; + + /* + * Non-numeric primitive types + */ + public static final byte BOOLEAN = 2; + public static final byte CHAR = 3; + + /* + * Numeric constants - deliberately arranged to allow for checking by using type >= BYTE + */ + public static final byte BYTE = 4; + public static final byte SHORT = 5; + public static final byte INT = 6; + public static final byte LONG = 7; + public static final byte FLOAT = 8; + public static final byte DOUBLE = 9; + + + static final Entry newAnyEntry(String tag, Object value) { + // DQH - To keep entry creation (e.g. map changes) as fast as possible, + // the entry construction is kept as simple as possible. + + // Prior versions of this code did type detection on value to + // recognize box types but that proved expensive. So now, + // the type is recorded as an ANY which is an indicator to do + // type detection later if need be. + return new Entry(tag, ANY, 0L, value); + } + + static final Entry newObjectEntry(String tag, Object value) { + return new Entry(tag, OBJECT, 0, value); + } + + static final Entry newBooleanEntry(String tag, boolean value) { + return new Entry(tag, BOOLEAN, boolean2Prim(value), Boolean.valueOf(value)); + } + + static final Entry newBooleanEntry(String tag, Boolean box) { + return new Entry(tag, BOOLEAN, boolean2Prim(box.booleanValue()), box); + } + + static final Entry newIntEntry(String tag, int value) { + return new Entry(tag, INT, int2Prim(value), null); + } + + static final Entry newIntEntry(String tag, Integer box) { + return new Entry(tag, INT, int2Prim(box.intValue()), box); + } + + static final Entry newLongEntry(String tag, long value) { + return new Entry(tag, LONG, long2Prim(value), null); + } + + static final Entry newLongEntry(String tag, Long box) { + return new Entry(tag, LONG, long2Prim(box.longValue()), box); + } + + static final Entry newFloatEntry(String tag, float value) { + return new Entry(tag, FLOAT, float2Prim(value), null); + } + + static final Entry newFloatEntry(String tag, Float box) { + return new Entry(tag, FLOAT, float2Prim(box.floatValue()), box); + } + + static final Entry newDoubleEntry(String tag, double value) { + return new Entry(tag, DOUBLE, double2Prim(value), null); + } + + static final Entry newDoubleEntry(String tag, Double box) { + return new Entry(tag, DOUBLE, double2Prim(box.doubleValue()), box); + } + + static final Entry newRemovalEntry(String tag) { + return new Entry(tag, REMOVED, 0, null); + } + + final String tag; + int hash; + + // To optimize construction of Entry around boxed primitives and Object entries, + // no type checks are done during construction. + // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into obj + // If an ANY entry is later type checked or request as a primitive, then the ANY will be resolved + // to the correct type. + + // From the outside perspective, this object remains functionally immutable. + // However, internally, it is important to remember that this type must be thread safe. + // That includes multiple threads racing to resolve an ANY entry at the same time. + + volatile byte type; + volatile long prim; + volatile Object obj; + + volatile String strCache = null; + + private Entry(String tag, byte type, long prim, Object obj) { + this.tag = tag; + this.hash = 0; // lazily computed + this.type = type; + this.prim = prim; + this.obj = obj; + } + + public final String tag() { + return this.tag; + } + + int hash() { + int hash = this.hash; + if ( hash != 0 ) return hash; + + hash = _hash(this.tag); + this.hash = hash; + return hash; + } + + public final byte type() { + return this.resolveAny(); + } + + public final boolean is(byte type) { + byte curType = this.type; + if ( curType == type ) { + return true; + } else if ( curType != ANY ) { + return false; + } else { + return (this.resolveAny() == type); + } + } + + public final boolean isNumericPrimitive() { + byte curType = this.type; + if ( _isNumericPrimitive(curType) ) { + return true; + } else if ( curType != ANY ) { + return false; + } else { + return _isNumericPrimitive(this.resolveAny()); + } + } + + public final boolean isNumber() { + byte curType = this.type; + return _isNumericPrimitive(curType) || (this.obj instanceof Number); + } + + private static final boolean _isNumericPrimitive(byte type) { + return (type >= BYTE); + } + + private final byte resolveAny() { + byte curType = this.type; + if ( curType != ANY ) return curType; + + Object value = this.obj; + long prim; + byte resolvedType; + + if (value instanceof Boolean) { + Boolean boolValue = (Boolean) value; + prim = boolean2Prim(boolValue); + resolvedType = BOOLEAN; + } else if (value instanceof Integer) { + Integer intValue = (Integer) value; + prim = int2Prim(intValue); + resolvedType = INT; + } else if (value instanceof Long) { + Long longValue = (Long) value; + prim = long2Prim(longValue); + resolvedType = LONG; + } else if (value instanceof Float) { + Float floatValue = (Float) value; + prim = float2Prim(floatValue); + resolvedType = FLOAT; + } else if (value instanceof Double) { + Double doubleValue = (Double) value; + prim = double2Prim(doubleValue); + resolvedType = DOUBLE; + } else { + prim = 0; + resolvedType = OBJECT; + } + + this._setPrim(resolvedType, prim); + + return resolvedType; + } + + private void _setPrim(byte type, long prim) { + // Order is important here, the contract is that prim must be set properly *before* + // type is set to a non-object type + + this.prim = prim; + this.type = type; + } + + public final boolean isObject() { + return this.is(OBJECT); + } + + public final boolean isRemoval() { + return this.is(REMOVED); + } + + public final boolean matches(String tag) { + return this.tag.equals(tag); + } + + public final Object objectValue() { + if ( this.obj != null ) { + return this.obj; + } + + // This code doesn't need to handle ANY-s. + // An entry that starts as an ANY will always have this.obj set + switch (this.type) { + case BOOLEAN: + this.obj = prim2Boolean(this.prim); + break; + + case INT: + // Maybe use a wider cache that handles response code??? + this.obj = prim2Int(this.prim); + break; + + case LONG: + this.obj = prim2Long(this.prim); + break; + + case FLOAT: + this.obj = prim2Float(this.prim); + break; + + case DOUBLE: + this.obj = prim2Double(this.prim); + break; + } + + if (this.is(REMOVED)) { + return null; + } + + return this.obj; + } + + public final boolean booleanValue() { + byte type = this.type; + + if (type == BOOLEAN) { + return prim2Boolean(this.prim); + } else if (type == ANY && this.obj instanceof Boolean) { + boolean boolValue = (Boolean)this.obj; + this._setPrim(BOOLEAN, boolean2Prim(boolValue)); + return boolValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case INT: + return prim2Int(prim) != 0; + + case LONG: + return prim2Long(prim) != 0L; + + case FLOAT: + return prim2Float(prim) != 0F; + + case DOUBLE: + return prim2Double(prim) != 0D; + + case OBJECT: + return (this.obj != null); + + case REMOVED: + return false; + } + + return false; + } + + public final int intValue() { + byte type = this.type; + + if (type == INT) { + return prim2Int(this.prim); + } else if (type == ANY && this.obj instanceof Integer) { + int intValue = (Integer)this.obj; + this._setPrim(INT, int2Prim(intValue)); + return intValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1 : 0; + + case LONG: + return (int) prim2Long(prim); + + case FLOAT: + return (int) prim2Float(prim); + + case DOUBLE: + return (int) prim2Double(prim); + + case OBJECT: + return 0; + + case REMOVED: + return 0; + } + + return 0; + } + + public final long longValue() { + byte type = this.type; + + if (type == LONG) { + return prim2Long(this.prim); + } else if (type == ANY && this.obj instanceof Long) { + long longValue = (Long)this.obj; + this._setPrim(LONG, long2Prim(longValue)); + return longValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1L : 0L; + + case INT: + return (long) prim2Int(prim); + + case FLOAT: + return (long) prim2Float(prim); + + case DOUBLE: + return (long) prim2Double(prim); + + case OBJECT: + return 0; + + case REMOVED: + return 0; + } + + return 0; + } + + public final float floatValue() { + byte type = this.type; + + if (type == FLOAT) { + return prim2Float(this.prim); + } else if (type == ANY && this.obj instanceof Float) { + float floatValue = (Float)this.obj; + this._setPrim(FLOAT, float2Prim(floatValue)); + return floatValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1F : 0F; + + case INT: + return (float) prim2Int(prim); + + case LONG: + return (float) prim2Long(prim); + + case DOUBLE: + return (float) prim2Double(prim); + + case OBJECT: + return 0F; + + case REMOVED: + return 0F; + } + + return 0F; + } + + public final double doubleValue() { + byte type = this.type; + + if (type == DOUBLE) { + return prim2Double(this.prim); + } else if (type == ANY && this.obj instanceof Double) { + double doubleValue = (Double)this.obj; + this._setPrim(DOUBLE, double2Prim(doubleValue)); + return doubleValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1D : 0D; + + case INT: + return (double) prim2Int(prim); + + case LONG: + return (double) prim2Long(prim); + + case FLOAT: + return (double) prim2Float(prim); + + case OBJECT: + return 0D; + + case REMOVED: + return 0D; + } + + return 0D; + } + + public final String stringValue() { + String strCache = this.strCache; + if ( strCache != null ) { + return strCache; + } + + String computeStr = this.computeStringValue(); + this.strCache = computeStr; + return computeStr; + } + + private final String computeStringValue() { + // Could do type resolution here, + // but decided to just fallback to this.obj.toString() for ANY case + switch (this.type) { + case BOOLEAN: + return Boolean.toString(prim2Boolean(this.prim)); + + case INT: + return Integer.toString(prim2Int(this.prim)); + + case LONG: + return Long.toString(prim2Long(this.prim)); + + case FLOAT: + return Float.toString(prim2Float(this.prim)); + + case DOUBLE: + return Double.toString(prim2Double(this.prim)); + + case REMOVED: + return null; + + case OBJECT: + case ANY: + return this.obj.toString(); + } + + return null; + } + + @Override + public final String toString() { + return this.tag() + '=' + this.stringValue(); + } + + @Deprecated + @Override + public String getKey() { + return this.tag(); + } + + @Deprecated + @Override + public Object getValue() { + return this.objectValue(); + } + + @Deprecated + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException(); + } + + private static final long boolean2Prim(boolean value) { + return value ? 1L : 0L; + } + + private static final boolean prim2Boolean(long prim) { + return (prim != 0L); + } + + private static final long int2Prim(int value) { + return (long) value; + } + + private static final int prim2Int(long prim) { + return (int) prim; + } + + private static final long long2Prim(long value) { + return value; + } + + private static final long prim2Long(long prim) { + return prim; + } + + private static final long float2Prim(float value) { + return (long) Float.floatToIntBits(value); + } + + private static final float prim2Float(long prim) { + return Float.intBitsToFloat((int) prim); + } + + private static final long double2Prim(double value) { + return Double.doubleToRawLongBits(value); + } + + private static final double prim2Double(long prim) { + return Double.longBitsToDouble(prim); + } + } + + public static final class Builder implements Iterable { + private Entry[] entries; + private int nextPos = 0; + + private Builder() { + this(8); + } + + private Builder(int size) { + this.entries = new Entry[size]; + } + + public final boolean isDefinitelyEmpty() { + return (this.nextPos == 0); + } + + /** + * Provides the estimated size of the map created by the builder + * Doesn't account for overwritten entries or entry removal + * @return + */ + public final int estimateSize() { + return this.nextPos; + } + + public final Builder put(String tag, Object value) { + return this.put(Entry.newAnyEntry(tag, value)); + } + + public final Builder put(String tag, CharSequence value) { + return this.put(Entry.newObjectEntry(tag, value)); + } + + public final Builder put(String tag, boolean value) { + return this.put(Entry.newBooleanEntry(tag, value)); + } + + public final Builder put(String tag, int value) { + return this.put(Entry.newIntEntry(tag, value)); + } + + public final Builder put(String tag, long value) { + return this.put(Entry.newLongEntry(tag, value)); + } + + public final Builder put(String tag, float value) { + return this.put(Entry.newFloatEntry(tag, value)); + } + + public final Builder put(String tag, double value) { + return this.put(Entry.newDoubleEntry(tag, value)); + } + + public final Builder remove(String tag) { + return this.put(Entry.newRemovalEntry(tag)); + } + + public final Builder put(Entry entry) { + if ( this.nextPos >= this.entries.length ) { + this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); + } + + this.entries[this.nextPos++] = entry; + return this; + } + + public final void reset() { + Arrays.fill(this.entries, null); + this.nextPos = 0; + } + + @Override + public final Iterator iterator() { + return new BuilderIterator(this.entries, this.nextPos); + } + + public TagMap build() { + TagMap map = new TagMap(); + if ( this.nextPos != 0 ) map.putAll(this.entries, this.nextPos); + return map; + } + + public TagMap buildImmutable() { + if ( this.nextPos == 0 ) { + return TagMap.EMPTY; + } else { + return this.build().freeze(); + } + } + } + + private static final class BuilderIterator implements Iterator { + private final Entry[] entries; + private final int size; + + private int pos; + + BuilderIterator(Entry[] entries, int size) { + this.entries = entries; + this.size = size; + + this.pos = -1; + } + + @Override + public final boolean hasNext() { + return ( this.pos + 1 < this.size ); + } + + @Override + public Entry next() { + if ( !this.hasNext() ) throw new NoSuchElementException("no next"); + + return this.entries[++this.pos]; + } + } + + private static abstract class MapIterator implements Iterator { + private final Object[] buckets; + + private Entry nextEntry; + + private int bucketIndex = -1; + + private BucketGroup group = null; + private int groupIndex = 0; + + MapIterator(TagMap map) { + this.buckets = map.buckets; + } + + @Override + public boolean hasNext() { + if ( this.nextEntry != null ) return true; + + while ( this.bucketIndex < this.buckets.length ) { + this.nextEntry = this.advance(); + if ( this.nextEntry != null ) return true; + } + + return false; + } + + Entry nextEntry() { + if ( this.nextEntry != null ) { + Entry nextEntry = this.nextEntry; + this.nextEntry = null; + return nextEntry; + } + + if ( this.hasNext() ) { + return this.nextEntry; + } else { + throw new NoSuchElementException(); + } + } + + private final Entry advance() { + while ( this.bucketIndex < this.buckets.length ) { + if ( this.group != null ) { + for ( ++this.groupIndex; this.groupIndex < BucketGroup.LEN; ++this.groupIndex ) { + Entry tagEntry = this.group._entryAt(this.groupIndex); + if ( tagEntry != null ) return tagEntry; + } + + // done processing - that group, go to next group + this.group = this.group.prev; + this.groupIndex = -1; + } + + // if the group is null, then we've finished the current bucket - so advance the bucket + if ( this.group == null ) { + for ( ++this.bucketIndex; this.bucketIndex < this.buckets.length; ++this.bucketIndex ) { + Object bucket = this.buckets[this.bucketIndex]; + + if ( bucket instanceof Entry ) { + return (Entry)bucket; + } else if ( bucket instanceof BucketGroup ) { + this.group = (BucketGroup)bucket; + this.groupIndex = -1; + + break; + } + } + } + }; + + return null; + } + } + + static final class EntryIterator extends MapIterator { + EntryIterator(TagMap map) { + super(map); + } + + @Override + public Entry next() { + return this.nextEntry(); + } + } + + /** + * BucketGroup is compromise for performance over a linked list or array + * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities + * - array - wouldn't be able to store hashes close together + * - parallel arrays (one for hashes & another for entries) would require more allocation + */ + static final class BucketGroup { + static final int LEN = 4; + + // int hashFilter = 0; + + // want the hashes together in the same cache line + int hash0 = 0; + int hash1 = 0; + int hash2 = 0; + int hash3 = 0; + + Entry entry0 = null; + Entry entry1 = null; + Entry entry2 = null; + Entry entry3 = null; + + BucketGroup prev = null; + + BucketGroup() {} + + /** + * New group with an entry pointing to existing BucketGroup + */ + BucketGroup(int hash0, Entry entry0, BucketGroup prev) { + this.hash0 = hash0; + this.entry0 = entry0; + + this.prev = prev; + + // this.hashFilter = hash0; + } + + /** + * New group composed of two entries + */ + BucketGroup(int hash0, Entry entry0, int hash1, Entry entry1) { + this.hash0 = hash0; + this.entry0 = entry0; + + this.hash1 = hash1; + this.entry1 = entry1; + + // this.hashFilter = hash0 | hash1; + } + + /** + * New group composed of 4 entries - used for cloning + */ + BucketGroup( + int hash0, Entry entry0, + int hash1, Entry entry1, + int hash2, Entry entry2, + int hash3, Entry entry3) + { + this.hash0 = hash0; + this.entry0 = entry0; + + this.hash1 = hash1; + this.entry1 = entry1; + + this.hash2 = hash2; + this.entry2 = entry2; + + this.hash3 = hash3; + this.entry3 = entry3; + + // this.hashFilter = hash0 | hash1 | hash2 | hash3; + } + + Entry _entryAt(int index) { + switch ( index ) { + case 0: + return this.entry0; + + case 1: + return this.entry1; + + case 2: + return this.entry2; + + case 3: + return this.entry3; + } + + return null; + } + + int _hashAt(int index) { + switch ( index ) { + case 0: + return this.hash0; + + case 1: + return this.hash1; + + case 2: + return this.hash2; + + case 3: + return this.hash3; + } + + return 0; + } + + int sizeInChain() { + int size = 0; + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + size += curGroup._size(); + } + return size; + } + + int _size() { + return ( this.hash0 == 0 ? 0 : 1 ) + + ( this.hash1 == 0 ? 0 : 1 ) + + ( this.hash2 == 0 ? 0 : 1 ) + + ( this.hash3 == 0 ? 0 : 1 ); + } + + boolean isEmptyChain() { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + if ( !curGroup._isEmpty() ) return false; + } + return true; + } + + boolean _isEmpty() { + return (this.hash0 | this.hash1 | this.hash2 | this.hash3) != 0; + // return (this.hashFilter == 0); + } + + BucketGroup findContainingGroupInChain(int hash, String tag) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + if ( curGroup._find(hash, tag) != null ) return curGroup; + } + return null; + } + +// boolean _mayContain(int hash) { +// return ((hash & this.hashFilter) == hash); +// } + + Entry findInChain(int hash, String tag) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + Entry curEntry = curGroup._find(hash, tag); + if ( curEntry != null ) return curEntry; + } + return null; + } + + Entry _find(int hash, String tag) { + // if ( this._mayContain(hash) ) return null; + + if ( this.hash0 == hash && this.entry0.matches(tag)) { + return this.entry0; + } else if ( this.hash1 == hash && this.entry1.matches(tag)) { + return this.entry1; + } else if ( this.hash2 == hash && this.entry2.matches(tag)) { + return this.entry2; + } else if ( this.hash3 == hash && this.entry3.matches(tag)) { + return this.entry3; + } + return null; + } + + BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) { + BucketGroup thisOrigHeadGroup = this; + BucketGroup thisNewestHeadGroup = thisOrigHeadGroup; + + for ( BucketGroup thatCurGroup = thatHeadGroup; + thatCurGroup != null; + thatCurGroup = thatCurGroup.prev ) + { + // First phase - tries to replace or insert each entry in the existing bucket chain + // Only need to search the original groups for replacements + // The whole chain is eligible for insertions + boolean handled0 = (thatCurGroup.hash0 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash0, thatCurGroup.entry0) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash0, thatCurGroup.entry0); + + boolean handled1 = (thatCurGroup.hash1 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash1, thatCurGroup.entry1) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash1, thatCurGroup.entry1); + + boolean handled2 = (thatCurGroup.hash2 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash2, thatCurGroup.entry2) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash2, thatCurGroup.entry2); + + boolean handled3 = (thatCurGroup.hash3 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash3, thatCurGroup.entry3) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash3, thatCurGroup.entry3); + + // Second phase - takes any entries that weren't handled by phase 1 and puts them + // into a new BucketGroup. Since BucketGroups are fixed size, we know that the + // left over entries from one BucketGroup will fit in the new BucketGroup. + if ( !handled0 || !handled1 || !handled2 || !handled3 ) { + // Rather than calling insert one time per entry + // Exploiting the fact that the new group is known to be empty + // And that BucketGroups are allowed to have holes in them (to allow for removal), + // so each unhandled entry from the source group is simply placed in + // the same slot in the new group + BucketGroup thisNewHashGroup = new BucketGroup(); + int hashFilter = 0; + if ( !handled0 ) { + thisNewHashGroup.hash0 = thatCurGroup.hash0; + thisNewHashGroup.entry0 = thatCurGroup.entry0; + hashFilter |= thatCurGroup.hash0; + } + if ( !handled1 ) { + thisNewHashGroup.hash1 = thatCurGroup.hash1; + thisNewHashGroup.entry1 = thatCurGroup.entry1; + hashFilter |= thatCurGroup.hash1; + } + if ( !handled2 ) { + thisNewHashGroup.hash2 = thatCurGroup.hash2; + thisNewHashGroup.entry2 = thatCurGroup.entry2; + hashFilter |= thatCurGroup.hash2; + } + if ( !handled3 ) { + thisNewHashGroup.hash3 = thatCurGroup.hash3; + thisNewHashGroup.entry3 = thatCurGroup.entry3; + hashFilter |= thatCurGroup.hash3; + } + // thisNewHashGroup.hashFilter = hashFilter; + thisNewHashGroup.prev = thisNewestHeadGroup; + + thisNewestHeadGroup = thisNewHashGroup; + } + } + + return thisNewestHeadGroup; + } + + boolean replaceOrInsertInChain(int hash, Entry entry) { + return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry); + } + + Entry replaceInChain(int hash, Entry entry) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + Entry prevEntry = curGroup._replace(hash, entry); + if ( prevEntry != null ) return prevEntry; + } + return null; + } + + Entry _replace(int hash, Entry entry) { + // if ( this._mayContain(hash) ) return null; + + // first check to see if the item is already present + Entry prevEntry = null; + if ( this.hash0 == hash && this.entry0.matches(entry.tag) ) { + prevEntry = this.entry0; + this.entry0 = entry; + } else if ( this.hash1 == hash && this.entry1.matches(entry.tag) ) { + prevEntry = this.entry1; + this.entry1 = entry; + } else if ( this.hash2 == hash && this.entry2.matches(entry.tag) ) { + prevEntry = this.entry2; + this.entry2 = entry; + } else if ( this.hash3 == hash && this.entry3.matches(entry.tag) ) { + prevEntry = this.entry3; + this.entry3 = entry; + } + + // no need to update this.hashFilter, since the hash is already included + return prevEntry; + } + + boolean insertInChain(int hash, Entry entry) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + if ( curGroup._insert(hash, entry) ) return true; + } + return false; + } + + boolean _insert(int hash, Entry entry) { + boolean inserted = false; + if ( this.hash0 == 0 ) { + this.hash0 = hash; + this.entry0 = entry; + + // this.hashFilter |= hash; + inserted = true; + } else if ( this.hash1 == 0 ) { + this.hash1 = hash; + this.entry1 = entry; + + // this.hashFilter |= hash; + inserted = true; + } else if ( this.hash2 == 0 ) { + this.hash2 = hash; + this.entry2 = entry; + + // this.hashFilter |= hash; + inserted = true; + } else if ( this.hash3 == 0 ) { + this.hash3 = hash; + this.entry3 = entry; + + // this.hashFilter |= hash; + inserted = true; + } + return inserted; + } + + BucketGroup removeGroupInChain(BucketGroup removeGroup) { + BucketGroup firstGroup = this; + if ( firstGroup == removeGroup ) { + return firstGroup.prev; + } + + for ( BucketGroup priorGroup = firstGroup, curGroup = priorGroup.prev; + curGroup != null; + priorGroup = curGroup, curGroup = priorGroup.prev ) { + if ( curGroup == removeGroup ) { + priorGroup.prev = curGroup.prev; + } + } + return firstGroup; + } + + Entry _remove(int hash, String tag) { + Entry existingEntry = null; + if ( this.hash0 == hash && this.entry0.matches(tag)) { + existingEntry = this.entry0; + + this.hash0 = 0; + this.entry0 = null; + } else if ( this.hash1 == hash && this.entry1.matches(tag) ) { + existingEntry = this.entry1; + + this.hash1 = 0; + this.entry1 = null; + } else if ( this.hash2 == hash && this.entry2.matches(tag) ) { + existingEntry = this.entry2; + + this.hash2 = 0; + this.entry2 = null; + } else if ( this.hash3 == hash && this.entry3.matches(tag) ) { + existingEntry = this.entry3; + + this.hash3 = 0; + this.entry3 = null; + } + return existingEntry; + } + + void forEachInChain(Consumer consumer) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._forEach(consumer); + } + } + + void _forEach(Consumer consumer) { + if ( this.entry0 != null ) consumer.accept(this.entry0); + if ( this.entry1 != null ) consumer.accept(this.entry1); + if ( this.entry2 != null ) consumer.accept(this.entry2); + if ( this.entry3 != null ) consumer.accept(this.entry3); + } + + void forEachInChain(T thisObj, BiConsumer consumer) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._forEach(thisObj, consumer); + } + } + + void _forEach(T thisObj, BiConsumer consumer) { + if ( this.entry0 != null ) consumer.accept(thisObj, this.entry0); + if ( this.entry1 != null ) consumer.accept(thisObj, this.entry1); + if ( this.entry2 != null ) consumer.accept(thisObj, this.entry2); + if ( this.entry3 != null ) consumer.accept(thisObj, this.entry3); + } + + void forEachInChain(T thisObj, U otherObj, TriConsumer consumer) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._forEach(thisObj, otherObj, consumer); + } + } + + void _forEach(T thisObj, U otherObj, TriConsumer consumer) { + if ( this.entry0 != null ) consumer.accept(thisObj, otherObj, this.entry0); + if ( this.entry1 != null ) consumer.accept(thisObj, otherObj, this.entry1); + if ( this.entry2 != null ) consumer.accept(thisObj, otherObj, this.entry2); + if ( this.entry3 != null ) consumer.accept(thisObj, otherObj, this.entry3); + } + + void fillMapFromChain(Map map) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._fillMap(map); + } + } + + void _fillMap(Map map) { + Entry entry0 = this.entry0; + if ( entry0 != null ) map.put(entry0.tag, entry0.objectValue()); + + Entry entry1 = this.entry1; + if ( entry1 != null ) map.put(entry1.tag, entry1.objectValue()); + + Entry entry2 = this.entry2; + if ( entry2 != null ) map.put(entry2.tag, entry2.objectValue()); + + Entry entry3 = this.entry3; + if ( entry3 != null ) map.put(entry3.tag, entry3.objectValue()); + } + + void fillStringMapFromChain(Map map) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._fillStringMap(map); + } + } + + void _fillStringMap(Map map) { + Entry entry0 = this.entry0; + if ( entry0 != null ) map.put(entry0.tag, entry0.stringValue()); + + Entry entry1 = this.entry1; + if ( entry1 != null ) map.put(entry1.tag, entry1.stringValue()); + + Entry entry2 = this.entry2; + if ( entry2 != null ) map.put(entry2.tag, entry2.stringValue()); + + Entry entry3 = this.entry3; + if ( entry3 != null ) map.put(entry3.tag, entry3.stringValue()); + } + + BucketGroup cloneChain() { + BucketGroup thisClone = this._cloneEntries(); + + BucketGroup thisPriorClone = thisClone; + for ( BucketGroup curGroup = this.prev; + curGroup != null; + curGroup = curGroup.prev ) + { + BucketGroup newClone = curGroup._cloneEntries(); + thisPriorClone.prev = newClone; + + thisPriorClone = newClone; + } + + return thisClone; + } + + BucketGroup _cloneEntries() { + return new BucketGroup( + this.hash0, this.entry0, + this.hash1, this.entry1, + this.hash2, this.entry2, + this.hash3, this.entry3); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(32); + builder.append('['); + for ( int i = 0; i < BucketGroup.LEN; ++i ) { + if ( builder.length() != 0 ) builder.append(", "); + + builder.append(this._entryAt(i)); + } + builder.append(']'); + return builder.toString(); + } + } + + private static final class Entries extends AbstractSet> { + private final TagMap map; + + Entries(TagMap map) { + this.map = map; + } + + @Override + public int size() { + return this.map.computeSize(); + } + + @Override + public boolean isEmpty() { + return this.map.checkIfEmpty(); + } + + @Override + public Iterator> iterator() { + @SuppressWarnings({"rawtypes", "unchecked"}) + Iterator> iter = (Iterator)this.map.iterator(); + return iter; + } + } + + private static final class Keys extends AbstractSet { + private final TagMap map; + + Keys(TagMap map) { + this.map = map; + } + + @Override + public int size() { + return this.map.computeSize(); + } + + @Override + public boolean isEmpty() { + return this.map.checkIfEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.map.containsKey(o); + } + + @Override + public Iterator iterator() { + return new KeysIterator(this.map); + } + } + + static final class KeysIterator extends MapIterator { + KeysIterator(TagMap map) { + super(map); + } + + @Override + public String next() { + return this.nextEntry().tag(); + } + } + + private static final class Values extends AbstractCollection { + private final TagMap map; + + Values(TagMap map) { + this.map = map; + } + + @Override + public int size() { + return this.map.computeSize(); + } + + @Override + public boolean isEmpty() { + return this.map.checkIfEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public Iterator iterator() { + return new ValuesIterator(this.map); + } + } + + static final class ValuesIterator extends MapIterator { + ValuesIterator(TagMap map) { + super(map); + } + + @Override + public Object next() { + return this.nextEntry().objectValue(); + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java index 28ddbe27ad3..296b93d0914 100644 --- a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java +++ b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java @@ -1,5 +1,6 @@ package datadog.trace.api.gateway; +import datadog.trace.api.TagMap; import datadog.trace.api.DDTraceId; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import java.util.Map; @@ -9,7 +10,7 @@ public interface IGSpanInfo { long getSpanId(); - Map getTags(); + TagMap getTags(); AgentSpan setTag(String key, boolean value); 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 63cd5afd2c9..ef102de8e5f 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,5 +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; @@ -229,11 +230,10 @@ interface ForPeerService { /** * Calculate the tags to be added to a span to represent the peer service * - * @param unsafeTags the span tags. Map che be mutated - * @return the input tags + * @param unsafeTags the span tags. Map to be mutated */ @Nonnull - Map tags(@Nonnull Map unsafeTags); + void tags(@Nonnull TagMap 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 b68496cc1f9..282261772e6 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,5 +1,6 @@ package datadog.trace.api.naming.v0; +import datadog.trace.api.TagMap; import datadog.trace.api.naming.NamingSchema; import java.util.Collections; import java.util.Map; @@ -13,7 +14,6 @@ public boolean supports() { @Nonnull @Override - public Map tags(@Nonnull final Map unsafeTags) { - return Collections.emptyMap(); + public void tags(@Nonnull final TagMap 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 47ff1ad9d29..4c64dda3a73 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,6 +1,7 @@ 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; @@ -52,8 +53,8 @@ public boolean supports() { return true; } - private void resolve(@Nonnull final Map unsafeTags) { - final Object component = unsafeTags.get(Tags.COMPONENT); + private void resolve(@Nonnull final TagMap unsafeTags) { + final Object component = unsafeTags.getObject(Tags.COMPONENT); // avoid issues with UTF8ByteString or others final String componentString = component == null ? null : component.toString(); final String override = overridesByComponent.get(componentString); @@ -71,14 +72,14 @@ private void resolve(@Nonnull final Map unsafeTags) { } private boolean resolveBy( - @Nonnull final Map unsafeTags, @Nullable final String[] precursors) { + @Nonnull final TagMap unsafeTags, @Nullable final String[] precursors) { if (precursors == null) { return false; } Object value = null; String source = null; for (String precursor : precursors) { - value = unsafeTags.get(precursor); + value = unsafeTags.getObject(precursor); if (value != null) { // we have a match. Use the tag name for the source source = precursor; @@ -90,7 +91,7 @@ private boolean resolveBy( return true; } - private void set(@Nonnull final Map unsafeTags, Object value, String source) { + private void set(@Nonnull final TagMap unsafeTags, Object value, String source) { if (value != null) { unsafeTags.put(Tags.PEER_SERVICE, value); unsafeTags.put(DDTags.PEER_SERVICE_SOURCE, source); @@ -99,13 +100,12 @@ private void set(@Nonnull final Map unsafeTags, Object value, St @Nonnull @Override - public Map tags(@Nonnull final Map unsafeTags) { + public void tags(@Nonnull final TagMap unsafeTags) { // check span.kind eligibility - final Object kind = unsafeTags.get(Tags.SPAN_KIND); + final Object kind = unsafeTags.getObject(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); } - return unsafeTags; } } diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java index 412ac7c85cc..ea62a78008d 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java @@ -2,6 +2,7 @@ import static datadog.trace.bootstrap.instrumentation.api.InternalContextKeys.SPAN_KEY; +import datadog.trace.api.TagMap; import datadog.context.Context; import datadog.context.ContextKey; import datadog.context.ImplicitContextKeyed; @@ -11,6 +12,8 @@ import datadog.trace.api.gateway.IGSpanInfo; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.interceptor.MutableSpan; + +import java.util.Collections; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -91,6 +94,9 @@ default boolean isValid() { @Override AgentSpan setSpanType(final CharSequence type); + @Override + TagMap getTags(); + Object getTag(String key); @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java index 84b9d55ecd1..7754a1f92de 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java @@ -1,6 +1,7 @@ package datadog.trace.bootstrap.instrumentation.api; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; @@ -108,19 +109,18 @@ public boolean isOutbound() { @Override public Object getTag(final String tag) { if (this.spanContext instanceof TagContext) { - return ((TagContext) this.spanContext).getTags().get(tag); + return ((TagContext) this.spanContext).getTags().getObject(tag); } return null; } @Override - public Map getTags() { + public TagMap getTags() { if (this.spanContext instanceof TagContext) { - Map tags = ((TagContext) this.spanContext).getTags(); - //noinspection unchecked,rawtypes - return (Map) tags; + return ((TagContext) this.spanContext).getTags(); + } else { + return TagMap.EMPTY; } - return Collections.emptyMap(); } @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java index 5d476c39b20..7cb834ca520 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java @@ -1,9 +1,8 @@ package datadog.trace.bootstrap.instrumentation.api; -import static java.util.Collections.emptyMap; - import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; @@ -79,10 +78,10 @@ public Integer getSamplingPriority() { public String getSpanType() { return null; } - + @Override - public Map getTags() { - return emptyMap(); + public TagMap getTags() { + return TagMap.EMPTY; } @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 226dca2bc30..0636a0932fe 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -3,6 +3,7 @@ import static datadog.trace.api.TracePropagationStyle.NONE; import static java.util.Collections.emptyList; +import datadog.trace.api.TagMap; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; @@ -24,7 +25,7 @@ public class TagContext implements AgentSpanContext.Extracted { private static final HttpHeaders EMPTY_HTTP_HEADERS = new HttpHeaders(); private final CharSequence origin; - private Map tags; + private TagMap tags; private List terminatedContextLinks; private Object requestContextDataAppSec; private Object requestContextDataIast; @@ -41,19 +42,22 @@ public TagContext() { this(null, null); } - public TagContext(final CharSequence origin, final Map tags) { + public TagContext(final CharSequence origin, final TagMap tags) { this(origin, tags, null, null, PrioritySampling.UNSET, null, NONE, DDTraceId.ZERO); } public TagContext( final CharSequence origin, - final Map tags, + final TagMap tags, final HttpHeaders httpHeaders, final Map baggage, final int samplingPriority, final TraceConfig traceConfig, final TracePropagationStyle propagationStyle, final DDTraceId traceId) { + + //if ( tags != null ) tags.checkWriteAccess(); + this.origin = origin; this.tags = tags; this.terminatedContextLinks = null; @@ -164,15 +168,16 @@ public String getCustomIpHeader() { return httpHeaders.customIpHeader; } - public final Map getTags() { - return tags; + public final TagMap getTags() { + // DQH - Because of the lazy in putTag, this method effectively returns an immutable map + return ( this.tags == null ) ? TagMap.EMPTY: this.tags; } public void putTag(final String key, final String value) { - if (this.tags.isEmpty()) { - this.tags = new TreeMap<>(); - } - this.tags.put(key, value); + if ( this.tags == null ) { + this.tags = new TagMap(); + } + this.tags.set(key, value); } @Override diff --git a/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy b/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy index ca1957ad6e0..7cdc25a22d9 100644 --- a/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy @@ -1,12 +1,13 @@ package datadog.trace.bootstrap.instrumentation.api import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap import spock.lang.Specification class ExtractedSpanTest extends Specification { def 'test extracted span from partial tracing context'() { given: - def tags = ['tag-1': 'value-1', 'tag-2': 'value-2'] + def tags = TagMap.fromMap(['tag-1': 'value-1', 'tag-2': 'value-2']) def baggage = ['baggage-1': 'value-1', 'baggage-2': 'value-2'] def traceId = DDTraceId.from(12345) def context = new TagContext('origin', tags, null, baggage, 0, null, null, traceId) From 6eefbca8a13034a767f35a2e0b16f2d6293a8751 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 18 Mar 2025 15:01:10 -0400 Subject: [PATCH 02/84] Making more locals final for consistency --- .../main/java/datadog/trace/core/CoreTracer.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 5f9fb7041c5..db9238bbc47 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 @@ -1486,7 +1486,9 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { } TagMap.Builder tagBuilder = this.tagBuilder; if (tagBuilder == null) { - this.tagBuilder = tagBuilder = TagMap.builder(); // Insertion order is important + // Insertion order is important, so using TagBuilder which builds up a set + // of Entry modifications in order + this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { tagBuilder.remove(tag); @@ -1544,13 +1546,13 @@ private DDSpanContext buildSpanContext() { final int samplingPriority; final CharSequence origin; final TagMap coreTags; - boolean coreTagsNeedsIntercept; + final boolean coreTagsNeedsIntercept; final TagMap rootSpanTags; - boolean rootSpanTagsNeedsIntercept; + final boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; - Object requestContextDataAppSec; - Object requestContextDataIast; - Object ciVisibilityContextData; + final Object requestContextDataAppSec; + final Object requestContextDataIast; + final Object ciVisibilityContextData; final PathwayContext pathwayContext; final PropagationTags propagationTags; From 81d3da68524875ce8ad7b036eee17234a19e1c76 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 18 Mar 2025 15:08:24 -0400 Subject: [PATCH 03/84] Taking final off of the product specific helper locals for now --- .../java/datadog/trace/core/CoreTracer.java | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) 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 db9238bbc47..ce7a14f38f6 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 @@ -941,7 +941,7 @@ long getTimeWithNanoTicks(long nanoTicks) { @Override public CoreSpanBuilder buildSpan( final String instrumentationName, final CharSequence operationName) { - return new CoreSpanBuilder(instrumentationName, operationName, this); + return new CoreSpanBuilder(this, instrumentationName, operationName); } @Override @@ -1347,7 +1347,7 @@ private static Map invertMap(Map map) { } /** Spans are built using this builder */ - public class CoreSpanBuilder implements AgentTracer.SpanBuilder { + public static class CoreSpanBuilder implements AgentTracer.SpanBuilder { private final String instrumentationName; private final CharSequence operationName; private final CoreTracer tracer; @@ -1368,7 +1368,10 @@ public class CoreSpanBuilder implements AgentTracer.SpanBuilder { private long spanId; CoreSpanBuilder( - final String instrumentationName, final CharSequence operationName, CoreTracer tracer) { + final CoreTracer tracer, + final String instrumentationName, + final CharSequence operationName) + { this.instrumentationName = instrumentationName; this.operationName = operationName; this.tracer = tracer; @@ -1409,7 +1412,7 @@ private void addTerminatedContextAsLinks() { public AgentSpan start() { AgentSpanContext pc = parent; if (pc == null && !ignoreScope) { - final AgentSpan span = activeSpan(); + final AgentSpan span = tracer.activeSpan(); if (span != null) { pc = span.context(); } @@ -1550,14 +1553,14 @@ private DDSpanContext buildSpanContext() { final TagMap rootSpanTags; final boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; - final Object requestContextDataAppSec; - final Object requestContextDataIast; - final Object ciVisibilityContextData; + Object requestContextDataAppSec; + Object requestContextDataIast; + Object ciVisibilityContextData; final PathwayContext pathwayContext; final PropagationTags propagationTags; if (this.spanId == 0) { - spanId = idGenerationStrategy.generateSpanId(); + spanId = tracer.idGenerationStrategy.generateSpanId(); } else { spanId = this.spanId; } @@ -1565,7 +1568,7 @@ private DDSpanContext buildSpanContext() { AgentSpanContext parentContext = parent; if (parentContext == null && !ignoreScope) { // use the Scope as parent unless overridden or ignored. - final AgentSpan activeSpan = scopeManager.activeSpan(); + final AgentSpan activeSpan = tracer.scopeManager.activeSpan(); if (activeSpan != null) { parentContext = activeSpan.context(); } @@ -1603,7 +1606,7 @@ private DDSpanContext buildSpanContext() { requestContextDataIast = null; ciVisibilityContextData = null; } - propagationTags = propagationTagsFactory.empty(); + propagationTags = tracer.propagationTagsFactory.empty(); } else { long endToEndStartTime; @@ -1619,19 +1622,19 @@ private DDSpanContext buildSpanContext() { } else if (parentContext != null) { traceId = parentContext.getTraceId() == DDTraceId.ZERO - ? idGenerationStrategy.generateTraceId() + ? tracer.idGenerationStrategy.generateTraceId() : parentContext.getTraceId(); parentSpanId = parentContext.getSpanId(); samplingPriority = parentContext.getSamplingPriority(); endToEndStartTime = 0; - propagationTags = propagationTagsFactory.empty(); + propagationTags = tracer.propagationTagsFactory.empty(); } else { // Start a new trace - traceId = idGenerationStrategy.generateTraceId(); + traceId = tracer.idGenerationStrategy.generateTraceId(); parentSpanId = DDSpanId.ZERO; samplingPriority = PrioritySampling.UNSET; endToEndStartTime = 0; - propagationTags = propagationTagsFactory.empty(); + propagationTags = tracer.propagationTagsFactory.empty(); } ConfigSnapshot traceConfig; @@ -1658,10 +1661,10 @@ private DDSpanContext buildSpanContext() { ciVisibilityContextData = null; } - rootSpanTags = localRootSpanTags; - rootSpanTagsNeedsIntercept = localRootSpanTagsNeedIntercept; + rootSpanTags = tracer.localRootSpanTags; + rootSpanTagsNeedsIntercept = tracer.localRootSpanTagsNeedIntercept; - parentTraceCollector = createTraceCollector(traceId, traceConfig); + parentTraceCollector = tracer.createTraceCollector(traceId, traceConfig); if (endToEndStartTime > 0) { parentTraceCollector.beginEndToEnd(endToEndStartTime); @@ -1676,11 +1679,11 @@ private DDSpanContext buildSpanContext() { && parentContext.getPathwayContext() != null && parentContext.getPathwayContext().isStarted() ? parentContext.getPathwayContext() - : dataStreamsMonitoring.newPathwayContext(); + : tracer.dataStreamsMonitoring.newPathwayContext(); // when removing fake services the best upward service name to pick is the local root one // since a split by tag (i.e. servlet context) might have happened on it. - if (!allowInferredServices) { + if (!tracer.allowInferredServices) { final DDSpan rootSpan = parentTraceCollector.getRootSpan(); serviceName = rootSpan != null ? rootSpan.getServiceName() : null; } @@ -1704,7 +1707,7 @@ private DDSpanContext buildSpanContext() { if (serviceName == null) { // it could be on the initial snapshot but may be overridden to null and service name // cannot be null - serviceName = CoreTracer.this.serviceName; + serviceName = tracer.serviceName; } final CharSequence operationName = @@ -1752,10 +1755,10 @@ private DDSpanContext buildSpanContext() { requestContextDataIast, ciVisibilityContextData, pathwayContext, - disableSamplingMechanismValidation, + tracer.disableSamplingMechanismValidation, propagationTags, - profilingContextIntegration, - injectBaggageAsTags, + tracer.profilingContextIntegration, + tracer.injectBaggageAsTags, isRemote); // By setting the tags on the context we apply decorators to any tags that have been set via From ca7b135b667c03b0f2cfb63fd855147be1c0fcf9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 10:46:47 -0400 Subject: [PATCH 04/84] Fixed bug in TagMap$BucketGroup#_isEmpty This bug causes remove to incorrectly remove a whole BucketGroup when the BucketGroup still contains entries. The intention was opposite only empty BucketGroups should be removed from the chain. Incorporated tests from prototype that caught this issue. --- .../main/java/datadog/trace/api/TagMap.java | 2 +- .../datadog/trace/api/TagMapBuilderTest.java | 106 +++++ .../datadog/trace/api/TagMapEntryTest.java | 258 ++++++++++ .../datadog/trace/api/TagMapFuzzTest.java | 444 ++++++++++++++++++ .../java/datadog/trace/api/TagMapTest.java | 371 +++++++++++++++ 5 files changed, 1180 insertions(+), 1 deletion(-) create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapTest.java diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 5b7b0b45934..f0839e1aacc 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1592,7 +1592,7 @@ boolean isEmptyChain() { } boolean _isEmpty() { - return (this.hash0 | this.hash1 | this.hash2 | this.hash3) != 0; + return (this.hash0 | this.hash1 | this.hash2 | this.hash3) == 0; // return (this.hashFilter == 0); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java new file mode 100644 index 00000000000..987a2507551 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -0,0 +1,106 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TagMapBuilderTest { + static final int SIZE = 32; + + @Test + public void buildMutable() { + TagMap.Builder builder = TagMap.builder(); + for ( int i = 0; i < SIZE; ++i ) { + builder.put(key(i), value(i)); + } + + assertEquals(SIZE, builder.estimateSize()); + + TagMap map = builder.build(); + for ( int i = 0; i < SIZE; ++i ) { + assertEquals(value(i), map.getString(key(i))); + } + assertEquals(SIZE, map.computeSize()); + + // just proving that the map is mutable + map.set(key(1000), value(1000)); + } + + @Test + public void buildImmutable() { + TagMap.Builder builder = TagMap.builder(); + for ( int i = 0; i < SIZE; ++i ) { + builder.put(key(i), value(i)); + } + + assertEquals(SIZE, builder.estimateSize()); + + TagMap map = builder.buildImmutable(); + for ( int i = 0; i < SIZE; ++i ) { + assertEquals(value(i), map.getString(key(i))); + } + assertEquals(SIZE, map.computeSize()); + + assertFrozen(map); + } + + @Test + public void buildWithRemoves() { + TagMap.Builder builder = TagMap.builder(); + for ( int i = 0; i < SIZE; ++i ) { + builder.put(key(i), value(i)); + } + + for ( int i = 0; i < SIZE; i += 2 ) { + builder.remove(key(i)); + } + + TagMap map = builder.build(); + for ( int i = 0; i < SIZE; ++i ) { + if ( (i % 2) == 0 ) { + assertNull(map.getString(key(i))); + } else { + assertEquals(value(i), map.getString(key(i))); + } + } + } + + @Test + public void reset() { + TagMap.Builder builder = TagMap.builder(2); + + builder.put(key(0), value(0)); + TagMap map0 = builder.build(); + + builder.reset(); + + builder.put(key(1), value(1)); + TagMap map1 = builder.build(); + + assertEquals(value(0), map0.getString(key(0))); + assertNull(map1.getString(key(0))); + + assertNull(map0.getString(key(1))); + assertEquals(value(1), map1.getString(key(1))); + } + + static final String key(int i) { + return "key-" + i; + } + + static final String value(int i) { + return "value-" + i; + } + + static final void assertFrozen(TagMap map) { + IllegalStateException ex = null; + try { + map.put("foo", "bar"); + } catch ( IllegalStateException e ) { + ex = e; + } + assertNotNull(ex); + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java new file mode 100644 index 00000000000..cfb1b9c4929 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -0,0 +1,258 @@ +package datadog.trace.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.Test; + +public class TagMapEntryTest { + @Test + public void objectEntry() { + TagMap.Entry entry = TagMap.Entry.newObjectEntry("foo", "bar"); + assertKey("foo", entry); + assertValue("bar", entry); + + assertEquals("bar", entry.stringValue()); + + assertTrue(entry.isObject()); + } + + @Test + public void anyEntry_object() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", "bar"); + + assertKey("foo", entry); + assertValue("bar", entry); + + assertTrue(entry.isObject()); + + assertKey("foo", entry); + assertValue("bar", entry); + } + + @Test + public void booleanEntry() { + TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", true); + + assertKey("foo", entry); + assertValue(true, entry); + + assertFalse(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + } + + @Test + public void booleanEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); + + assertKey("foo", entry); + assertValue(true, entry); + + assertFalse(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + } + + @Test + public void anyEntry_boolean() { + TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); + + assertKey("foo", entry); + assertValue(true, entry); + + assertFalse(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + + assertValue(true, entry); + } + + @Test + public void intEntry() { + TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", 20); + + assertKey("foo", entry); + assertValue(20, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.INT)); + } + + @Test + public void intEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)); + + assertKey("foo", entry); + assertValue(20, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.INT)); + } + + @Test + public void anyEntry_int() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)); + + assertKey("foo", entry); + assertValue(20, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.INT)); + + assertValue(20, entry); + } + + @Test + public void longEntry() { + TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", 1_048_576L); + + assertKey("foo", entry); + assertValue(1_048_576L, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.LONG)); + } + + @Test + public void longEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)); + + assertKey("foo", entry); + assertValue(1_048_576L, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.LONG)); + } + + @Test + public void anyEntry_long() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)); + + assertKey("foo", entry); + assertValue(1_048_576L, entry); + + // type checks force any resolution + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.LONG)); + + // check value again after resolution + assertValue(1_048_576L, entry); + } + + @Test + public void doubleEntry() { + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); + + assertKey("foo", entry); + assertValue(Math.PI, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.DOUBLE)); + } + + @Test + public void doubleEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); + + assertKey("foo", entry); + assertValue(Math.PI, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.DOUBLE)); + } + + @Test + public void anyEntry_double() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)); + + assertKey("foo", entry); + assertValue(Math.PI, entry); + + // type checks force any resolution + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.DOUBLE)); + + // check value again after resolution + assertValue(Math.PI, entry); + } + + @Test + public void floatEntry() { + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); + + assertKey("foo", entry); + assertValue(2.718281828f, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.FLOAT)); + } + + @Test + public void floatEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); + + assertKey("foo", entry); + assertValue(2.718281828f, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.FLOAT)); + } + + static final void assertKey(String expected, TagMap.Entry entry) { + assertEquals(expected, entry.tag()); + assertEquals(expected, entry.getKey()); + } + + static final void assertValue(Object expected, TagMap.Entry entry) { + assertEquals(expected, entry.objectValue()); + assertEquals(expected, entry.getValue()); + + assertEquals(expected.toString(), entry.stringValue()); + } + + static final void assertValue(boolean expected, TagMap.Entry entry) { + assertEquals(expected, entry.booleanValue()); + assertEquals(Boolean.valueOf(expected), entry.objectValue()); + + assertEquals(Boolean.toString(expected), entry.stringValue()); + } + + static final void assertValue(int expected, TagMap.Entry entry) { + assertEquals(expected, entry.intValue()); + assertEquals((long)expected, entry.longValue()); + assertEquals((float)expected, entry.floatValue()); + assertEquals((double)expected, entry.doubleValue()); + assertEquals(Integer.valueOf(expected), entry.objectValue()); + + assertEquals(Integer.toString(expected), entry.stringValue()); + } + + static final void assertValue(long expected, TagMap.Entry entry) { + assertEquals(expected, entry.longValue()); + assertEquals((int)expected, entry.intValue()); + assertEquals((float)expected, entry.floatValue()); + assertEquals((double)expected, entry.doubleValue()); + assertEquals(Long.valueOf(expected), entry.objectValue()); + + assertEquals(Long.toString(expected), entry.stringValue()); + } + + static final void assertValue(double expected, TagMap.Entry entry) { + assertEquals(expected, entry.doubleValue()); + assertEquals((int)expected, entry.intValue()); + assertEquals((long)expected, entry.longValue()); + assertEquals((float)expected, entry.floatValue()); + assertEquals(Double.valueOf(expected), entry.objectValue()); + + assertEquals(Double.toString(expected), entry.stringValue()); + } + + static final void assertValue(float expected, TagMap.Entry entry) { + assertEquals(expected, entry.floatValue()); + assertEquals((int)expected, entry.intValue()); + assertEquals((long)expected, entry.longValue()); + assertEquals((double)expected, entry.doubleValue()); + assertEquals(Float.valueOf(expected), entry.objectValue()); + + assertEquals(Float.toString(expected), entry.stringValue()); + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java new file mode 100644 index 00000000000..053997f7bd4 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -0,0 +1,444 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.Test; + +public final class TagMapFuzzTest { + static final int NUM_KEYS = 128; + static final int MAX_NUM_ACTIONS = 32; + + @Test + void test() { + test(generateTest()); + } + + @Test + void testMerge() { + TestCase mapACase = generateTest(); + TestCase mapBCase = generateTest(); + + TagMap tagMapA = test(mapACase); + TagMap tagMapB = test(mapBCase); + + HashMap hashMapA = new HashMap<>(tagMapA); + HashMap hashMapB = new HashMap<>(tagMapB); + + tagMapA.putAll(tagMapB); + hashMapA.putAll(hashMapB); + + assertMapEquals(hashMapA, tagMapA); + } + + @Test + void priorFailingCase0() { + TagMap map = makeTagMap( + remove("key-4"), + put("key-71","values-443049055"), + put("key-2","values-1227065898"), + put("key-25","values-696891692"), + put("key-93","values-763707175"), + put("key-23","values--1514091210"), + put("key-16","values--1388742686") + ); + + MapAction failingAction = putAllTagMap("key-17","values--2085338893","key-51","values-960243765","key-33","values-1493544499","key-46","values-697926849","key-70","values--184054454","key-67","values-374577326","key-9","values--742453833","key-11","values-1606950841","key-119","values--1914593057","key-53","values-375236438","key-96","values--107185569","key-47","values--1276407408","key-125","values--1627172151","key-110","values--1227150283","key-15","values-380379920","key-42","values--632271048","key-99","values--650090786","key-8","values--1990889145","key-103","values-1815698254","key-120","values-279025031","key-93","values-589795963","key-12","values--935895941","key-105","values-94976227","key-85","values--424609970","key-78","values-1231948102","key-115","values-88670282","key-26","values-733903384","key-100","values-2102967487","key-74","values-958598087","key-104","values-264458254","key-125","values--1781797927","key-27","values--562810078","key-7","values--376776745","key-111","values-263564677","key-50","values--859673100","key-57","values-1585057281","key-48","values--617889787","key-98","values--1878108220","key-9","values--227223375","key-59","values-1577082288","key-94","values--268049040","key-0","values-1708355496","key-62","values--733451297","key-14","values-232732747","key-4","values--406605642","key-58","values-1772476833","key-8","values--1155025225","key-101","values-144480545","key-66","values-355117269","key-121","values-1858008722","key-33","values-1947754079","key-1","values--1475603838","key-125","values--2146772243","key-117","values-852022714","key-53","values--2039348506","key-65","values-2011228657","key-108","values-1581592518","key-17","values-2129571020","key-5","values-1106900841","key-80","values-1791757923","key-18","values--1992962227","key-2","values-328863878","key-110","values-1182949334","key-5","values-1049403346","key-107","values-1246502060","key-115","values-2053931423","key-19","values--1731179633","key-104","values--1090790550","key-67","values--1312759979","key-10","values-1411135","key-109","values--1784920248","key-20","values--827644780","key-55","values--1610270998","key-60","values-1287959520","key-31","values-1686541667","key-41","values-399844058","key-115","values-2045201464","key-78","values-358081227","key-57","values--1374149269","key-65","values-1871734555","key-124","values--211494558","key-119","values-1757597102","key-32","values--336988038","key-85","values-1415155858","key-44","values-1455425178","key-48","values--325658059","key-68","values--793590840","key-96","values--2010766492","key-40","values-2007171160","key-29","values-186945230","key-63","values-1741962849","key-26","values-948582805","key-31","values-47004766","key-90","values-1304302008","key-69","values-2120328211","key-111","values-2053321468","key-69","values--498524858","key-125","values--193004619","key-30","values--1142090845","key-15","values--1334900170","key-33","values-1011001500","key-55","values-452401605","key-18","values-1260118555","key-44","values--1109396459","key-2","values--555647718","key-61","values-1060742038","key-51","values--827099230","key-62","values--1443716296","key-16","values-534556355","key-81","values--787910427","key-20","values-1429697120","key-36","values--1775988293","key-66","values-624669635","key-25","values--684183265","key-26","values-293626449","key-91","values--1212867803","key-6","values-1778251481","key-83","values-1257370908","key-92","values--1120490028","key-111","values-9646496","key-90","values-1485206899"); + failingAction.apply(map); + failingAction.verify(map); + } + + @Test + void priorFailingCase1() { + TagMap map = makeTagMap( + put("key-68","values--37178328"), + put("key-93","values--2093086281") + ); + + MapAction failingAction = putAllTagMap("key-36","values--1951535044","key-59","values--1045985660","key-68","values-1270827526","key-65","values-440073158","key-91","values-954365843","key-75","values-1014366449","key-117","values--1306617705","key-90","values-984567966","key-120","values--1802603599","key-56","values-319574488","key-78","values--711288173","key-103","values-694279462","key-84","values-1391260657","key-59","values--484807195","key-67","values-1675498322","key-91","values--227731796","key-105","values--1471022333","key-112","values--755617374","key-117","values--668324524","key-65","values-1165174761","key-13","values--1947081814","key-72","values-2032502631","key-106","values-256372025","key-71","values--995163162","key-92","values-972782926","key-116","values-25012447","key-23","values--979671053","key-94","values-367125724","key-48","values--2011523144","key-14","values-578926680","key-65","values-1325737627","key-89","values-1539092266","key-100","values--319629978","key-53","values-1125496255","key-2","values-1988036327","key-105","values--1333468536","key-37","values-351345678","key-4","values-683252782","key-62","values--1466612877","key-100","values-268100559","key-104","values-3517495","key-48","values--1588410835","key-42","values--180653405","key-118","values--1181647255","key-17","values-509279769","key-33","values-298668287","key-76","values-2062435628","key-18","values-287811864","key-46","values--1337930894","key-50","values-2089310564","key-24","values--1870293199","key-47","values--1155431370","key-81","values--1507929564","key-115","values-1149614815","key-57","values--334611395","key-86","values-146447703","key-107","values-938082683","key-38","values-338654203","key-40","values--376260149","key-20","values--860844060","key-20","values-2003129702","key-75","values--1787311067","key-39","values--1988768973","key-58","values--479797619","key-16","values-571033631","key-65","values--1867296166","key-56","values--2071960469","key-12","values-821930484","key-40","values--54692885","key-65","values-328817493","key-121","values-1276016318","key-33","values--2081652233","key-31","values-381335133","key-77","values-1486312656","key-48","values--1058365372","key-109","values--733344537","key-85","values-1236864082","key-35","values-2045087594","key-49","values-1990762822","key-38","values--1582706513","key-18","values--626997990","key-80","values--1995264473","key-126","values--558193472","key-83","values-415016167","key-53","values-1348674948","key-58","values-612738550","key-12","values-417676134","key-101","values--58098778","key-127","values-1658306930","key-17","values-985378289","key-68","values-686600535","key-36","values-365513638","key-87","values--1737233661","key-67","values--1840935230","key-8","values-540289596","key-11","values--2045114386","key-38","values--786598887","key-48","values-1877144385","key-5","values-65838542","key-18","values-263200779","key-120","values--1500947489","key-65","values-769990109","key-38","values-1886840000","key-29","values--48760205","key-61","values--1942966789"); + failingAction.apply(map); + failingAction.verify(map); + } + + @Test + void priorFailingCase2() { + TestCase testCase = new TestCase( + remove("key-34"), + put("key-122","values-1828753938"), + putAll("key-123","values--118789056","key-28","values--751841781","key-105","values-1663318183","key-63","values--2036414463","key-74","values-1584612783","key-118","values--414681411","key-67","values-1154668404","key-1","values--1755856616","key-89","values--344740102","key-110","values-1884649283","key-1","values--1420345075","key-22","values-1951712698","key-103","values-488559164","key-8","values-1180668912","key-44","values-290310046","key-105","values--303926067","key-26","values-910376351","key-59","values-1600204544","key-23","values-425861746","key-76","values--1045446587","key-21","values-453905226","key-1","values-286624672","key-69","values-934359656","key-57","values--1890465763","key-13","values--1949062639","key-68","values-242077328","key-42","values--1584075743","key-46","values--1306318288","key-31","values--848418043","key-71","values--1547961101","key-121","values--1493693636","key-24","values-330660358","key-24","values--1466871690","key-91","values--995064376","key-18","values-1615316779","key-124","values--296191510","key-52","values-740309054","key-8","values-1777392898","key-73","values-92831985","key-13","values--1711360891","key-114","values-1960346620","key-44","values--1599497099","key-107","values-668485357","key-116","values--1792788504"), + put("key-123","values--1844485682"), + putAll("key-64","values--1694520036","key-17","values--469732912","key-79","values--1293521097","key-11","values--2000592955","key-98","values-517073723","key-28","values-1085152681","key-34","values-1943586726","key-3","values-216087991","key-97","values-222660872","key-41","values-90906196","key-63","values--934208984","key-57","values-327167184","key-111","values--1059115125","key-75","values--2031064209","key-8","values-1924310140","key-69","values--362514182","key-90","values-852043703","key-98","values--998302860","key-49","values-1658920804","key-106","values--227162298","key-25","values-493046373","key-52","values--555623542","key-77","values--717275660","key-31","values-1930766287","key-69","values--1367213079","key-38","values--1112081116","key-65","values--1916889923","key-96","values-157036191","key-127","values--302553995","key-38","values-485874872","key-110","values--855874569","key-39","values--390829775","key-7","values--452123269","key-63","values--527204905","key-101","values-166173307","key-126","values-1050454498","key-4","values--215188400","key-25","values-947961204","key-42","values-145803888","key-1","values--970532578","key-43","values--1675493776","key-29","values-1193328809","key-108","values-1302659140","key-120","values--1722764270","key-24","values--483238806","key-53","values-611589672","key-39","values--229429656","key-29","values--733337788","key-9","values-736222322","key-74","values--950770749","key-91","values-202817768","key-95","values-500260096","key-71","values--1798188865","key-12","values--1936098297","key-28","values--2116134632","key-21","values-799594067","key-68","values--333178107","key-50","values-445767791","key-88","values-1307699662","key-69","values--110615017","key-25","values-699603233","key-101","values--2093413536","key-91","values--2022040839","key-45","values-888546703","key-40","values--2140684954","key-1","values-371033654","key-68","values--20293415","key-59","values-697437101","key-43","values--1145022834","key-62","values--2125187195","key-15","values--1062944166","key-103","values--889634836","key-125","values-8694763","key-101","values--281475498","key-13","values-1972488719","key-32","values-1900833863","key-119","values--926978044","key-82","values-288820151","key-78","values--303310027","key-25","values--1284661437","key-47","values-1624726045","key-14","values-1658036950","key-65","values-1629683219","key-10","values-275264679","key-126","values--592085694","key-32","values-1844385705","key-85","values--1815321660","key-72","values-918231225","key-91","values-675699466","key-121","values--2008685332","key-61","values--1398921570","key-19","values-617817427","key-122","values--793708860","key-41","values--2027225350","key-41","values-1194206680","key-1","values-1116090448","key-49","values-1662444555","key-54","values-747436284","key-118","values--1367237858","key-65","values-133495093","key-73","values--1451855551","key-43","values--357794833","key-76","values-129403123","key-59","values--65688873","key-22","values-480031738","key-73","values--310815862","key-0","values--1734944386","key-56","values--540459893","key-38","values-1308912555","key-2","values--2073028093","key-14","values--693713438","key-76","values-295450436","key-113","values--2065146687","key-0","values-2076623027","key-17","values--1394046356","key-78","values--2014478659","key-5","values--665180960"), + put("key-124","values-460160716"), + put("key-112","values--1828904046"), + put("key-41","values--904162962")); + + Map expected = makeMap(testCase); + TagMap actual = makeTagMap(testCase); + + MapAction failingAction = remove("key-127"); + failingAction.apply(expected); + failingAction.verify(expected); + + failingAction.apply(actual); + failingAction.verify(actual); + + assertMapEquals(expected, actual); + } + + public static final TagMap test(MapAction... actions) { + return test(new TestCase(Arrays.asList(actions))); + } + + public static final Map makeMap(TestCase testCase) { + return makeMap(testCase.actions); + } + + public static final Map makeMap(MapAction... actions) { + return makeMap(Arrays.asList(actions)); + } + + public static final Map makeMap(List actions) { + Map map = new HashMap<>(); + for ( MapAction action: actions ) { + action.apply(map); + } + return map; + } + + public static final TagMap makeTagMap(TestCase testCase) { + return makeTagMap(testCase.actions); + } + + public static final TagMap makeTagMap(MapAction... actions) { + return makeTagMap(Arrays.asList(actions)); + } + + public static final TagMap makeTagMap(List actions) { + TagMap map = new TagMap(); + for ( MapAction action: actions ) { + action.apply(map); + } + return map; + } + + public static final TagMap test(TestCase test) { + List actions = test.actions(); + + Map hashMap = new HashMap<>(); + TagMap tagMap = new TagMap(); + + int actionIndex = 0; + try { + for ( actionIndex = 0; actionIndex < actions.size(); ++actionIndex ) { + MapAction action = actions.get(actionIndex); + + Object expected = action.apply(hashMap); + Object result = action.apply(tagMap); + + assertEquals(expected, result); + + action.verify(tagMap); + + assertMapEquals(hashMap, tagMap); + } + } catch ( Error e ) { + System.err.println(new TestCase(actions.subList(0, actionIndex + 1))); + + throw e; + } + return tagMap; + } + + public static final TestCase generateTest() { + return generateTest(ThreadLocalRandom.current().nextInt(MAX_NUM_ACTIONS)); + } + + public static final TestCase generateTest(int size) { + List actions = new ArrayList<>(size); + for ( int i = 0; i < size; ++i ) { + actions.add(randomAction()); + } + return new TestCase(actions); + } + + public static final MapAction randomAction() { + float actionSelector = ThreadLocalRandom.current().nextFloat(); + + if ( actionSelector > 0.5 ) { + // 50% puts + return put(randomKey(), randomValue()); + } else if ( actionSelector > 0.3 ) { + // 20% removes + return remove(randomKey()); + } else if ( actionSelector > 0.2 ) { + // 10% putAll TagMap + return putAllTagMap(randomKeysAndValues()); + } else if ( actionSelector > 0.02 ) { + // ~10% putAll HashMap + return putAll(randomKeysAndValues()); + } else { + return clear(); + } + } + + public static final MapAction put(String key, String value) { + return new Put(key, value); + } + + public static final MapAction putAll(String... keysAndValues) { + return new PutAll(keysAndValues); + } + + public static final MapAction putAllTagMap(String... keysAndValues) { + return new PutAllTagMap(keysAndValues); + } + + public static final MapAction clear() { + return Clear.INSTANCE; + } + + public static final MapAction remove(String key) { + return new Remove(key); + } + + static final void assertMapEquals(Map expected, TagMap actual) { + // checks entries in both directions to make sure there's full intersection + + for ( Map.Entry expectedEntry: expected.entrySet() ) { + TagMap.Entry actualEntry = actual.getEntry(expectedEntry.getKey()); + assertNotNull(actualEntry); + assertEquals(expectedEntry.getValue(), actualEntry.getValue()); + } + + for ( TagMap.Entry actualEntry: actual ) { + Object expectedValue = expected.get(actualEntry.tag()); + assertEquals(expectedValue, actualEntry.objectValue()); + } + } + + static final String randomKey() { + return "key-" + ThreadLocalRandom.current().nextInt(NUM_KEYS); + } + + static final String randomValue() { + return "values-" + ThreadLocalRandom.current().nextInt(); + } + + static final String[] randomKeysAndValues() { + int numEntries = ThreadLocalRandom.current().nextInt(NUM_KEYS); + + String[] keysAndValues = new String[numEntries << 1]; + for ( int i = 0; i < keysAndValues.length; i += 2 ) { + keysAndValues[i] = randomKey(); + keysAndValues[i + 1] = randomValue(); + } + return keysAndValues; + } + + static final String literal(String str) { + return "\"" + str + "\""; + } + + static final String literalVarArgs(String... strs) { + StringBuilder builder = new StringBuilder(); + for ( String str: strs ) { + if ( builder.length() != 0 ) builder.append(','); + builder.append(literal(str)); + } + return builder.toString(); + } + + static final Map mapOf(String... keysAndValues) { + HashMap map = new HashMap<>(keysAndValues.length >> 1); + for ( int i = 0; i < keysAndValues.length; i += 2 ) { + String key = keysAndValues[i]; + String value = keysAndValues[i + 1]; + + map.put(key, value); + } + return map; + } + + static final TagMap tagMapOf(String... keysAndValues) { + TagMap map = new TagMap(); + for ( int i = 0; i < keysAndValues.length; i += 2 ) { + String key = keysAndValues[i]; + String value = keysAndValues[i + 1]; + + map.set(key, value); + // map.check(); + } + return map; + } + + static final class TestCase { + final List actions; + + TestCase(MapAction... actions) { + this.actions = Arrays.asList(actions); + } + + TestCase(List actions) { + this.actions = actions; + } + + public final List actions() { + return this.actions; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for ( MapAction action: this.actions ) { + builder.append(action).append(',').append('\n'); + } + return builder.toString(); + } + } + + static abstract class MapAction { + public abstract Object apply(Map mapUnderTest); + + public abstract void verify(Map mapUnderTest); + + public abstract String toString(); + } + + static final class Put extends MapAction { + final String key; + final String value; + + Put(String key, String value) { + this.key = key; + this.value = value; + } + + @Override + public Object apply(Map mapUnderTest) { + return mapUnderTest.put(this.key, this.value); + } + + @Override + public void verify(Map mapUnderTest) { + assertEquals(this.value, mapUnderTest.get(this.key)); + } + + @Override + public String toString() { + return String.format("put(%s,%s)", literal(this.key), literal(this.value)); + } + } + + static final class PutAll extends MapAction { + final String[] keysAndValues; + final Map map; + + PutAll(String... keysAndValues) { + this.keysAndValues = keysAndValues; + this.map = mapOf(keysAndValues); + } + + @Override + public Object apply(Map mapUnderTest) { + mapUnderTest.putAll(this.map); + + return void.class; + } + + @Override + public void verify(Map mapUnderTest) { + for ( Map.Entry entry: this.map.entrySet() ) { + assertEquals(entry.getValue(), mapUnderTest.get(entry.getKey())); + } + } + + @Override + public String toString() { + return String.format("putAll(%s)", literalVarArgs(this.keysAndValues)); + } + } + + static final class PutAllTagMap extends MapAction { + final String[] keysAndValues; + final TagMap tagMap; + + PutAllTagMap(String... keysAndValues) { + this.keysAndValues = keysAndValues; + this.tagMap = tagMapOf(keysAndValues); + } + + @Override + public Object apply(Map mapUnderTest) { + mapUnderTest.putAll(this.tagMap); + + return void.class; + } + + @Override + public void verify(Map mapUnderTest) { + for ( TagMap.Entry entry: this.tagMap ) { + assertEquals(entry.objectValue(), mapUnderTest.get(entry.tag()), "key=" + entry.tag()); + } + } + + @Override + public String toString() { + return String.format("putAllTagMap(%s)", literalVarArgs(this.keysAndValues)); + } + } + + static final class Remove extends MapAction { + final String key; + + Remove(String key) { + this.key = key; + } + + @Override + public Object apply(Map mapUnderTest) { + return mapUnderTest.remove(this.key); + } + + @Override + public void verify(Map mapUnderTest) { + assertFalse(mapUnderTest.containsKey(this.key)); + } + + @Override + public String toString() { + return String.format("remove(%s)", literal(this.key)); + } + } + + static final class Clear extends MapAction { + static final Clear INSTANCE = new Clear(); + + private Clear() {} + + @Override + public Object apply(Map mapUnderTest) { + mapUnderTest.clear(); + + return void.class; + } + + @Override + public void verify(Map mapUnderTest) { + assertTrue(mapUnderTest.isEmpty()); + } + + @Override + public String toString() { + return "clear()"; + } + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java new file mode 100644 index 00000000000..4be1b76dcc9 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -0,0 +1,371 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.Test; + +public class TagMapTest { + // size is chosen to make sure to stress all types of collisions in the Map + static final int MANY_SIZE = 256; + + @Test + public void map_put() { + TagMap map = new TagMap(); + + Object prev = map.put("foo", "bar"); + assertNull(prev); + + assertEntry("foo", "bar", map); + + assertSize(1, map); + assertNotEmpty(map); + } + + @Test + public void clear() { + int size = randomSize(); + + TagMap map = createTagMap(size); + assertSize(size, map); + assertNotEmpty(map); + + map.clear(); + assertSize(0, map); + assertEmpty(map); + } + + @Test + public void map_put_replacement() { + TagMap map = new TagMap(); + Object prev1 = map.put("foo", "bar"); + assertNull(prev1); + + assertEntry("foo", "bar", map); + assertSize(1, map); + assertNotEmpty(map); + + Object prev2 = map.put("foo", "baz"); + assertEquals("bar", prev2); + + assertEntry("foo", "baz", map); + } + + @Test + public void map_remove() { + TagMap map = new TagMap(); + + Object prev1 = map.remove("foo"); + assertNull(prev1); + + map.put("foo", "bar"); + assertEntry("foo", "bar", map); + assertSize(1, map); + assertNotEmpty(map); + + Object prev2 = map.remove("foo"); + assertEquals("bar", prev2); + assertSize(0, map); + assertEmpty(map); + } + + @Test + public void freeze() { + TagMap map = new TagMap(); + map.put("foo", "bar"); + + assertEntry("foo", "bar", map); + + map.freeze(); + + assertFrozen(() -> { + map.remove("foo"); + }); + + assertEntry("foo", "bar", map); + } + + @Test + public void emptyMap() { + TagMap map = TagMap.EMPTY; + + assertSize(0, map); + assertEmpty(map); + + assertFrozen(map); + } + + + @Test + public void putMany() { + int size = randomSize(); + TagMap map = createTagMap(size); + + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), map); + } + + assertNotEmpty(map); + assertSize(size, map); + } + + @Test + public void cloneMany() { + int size = randomSize(); + TagMap orig = createTagMap(size); + + TagMap copy = orig.copy(); + orig.clear(); // doing this to make sure that copied isn't modified + + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), copy); + } + } + + @Test + public void replaceALot() { + int size = randomSize(); + TagMap map = createTagMap(size); + + for ( int i = 0; i < size; ++i ) { + int index = ThreadLocalRandom.current().nextInt(size); + + map.put(key(index), altValue(index)); + assertEquals(altValue(index), map.get(key(index))); + } + } + + @Test + public void shareEntry() { + TagMap orig = new TagMap(); + orig.set("foo", "bar"); + + TagMap dest = new TagMap(); + dest.putEntry(orig.getEntry("foo")); + + assertSame(orig.getEntry("foo"), dest.getEntry("foo")); + } + + @Test + public void putAll() { + int size = 67; + TagMap orig = createTagMap(size); + + TagMap dest = new TagMap(); + for ( int i = size - 1; i >= 0 ; --i ) { + dest.set(key(i), altValue(i)); + } + + // This should clobber all the values in dest + dest.putAll(orig); + + // assertSize(size, dest); + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), dest); + } + } + + @Test + public void removeMany() { + int size = randomSize(); + TagMap map = createTagMap(size); + + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), map); + } + + assertNotEmpty(map); + assertSize(size, map); + + for ( int i = 0; i < size; ++i ) { + Object removedValue = map.remove(key(i)); + assertEquals(value(i), removedValue); + + // not doing exhaustive size checks + assertEquals(size - i - 1, map.computeSize()); + } + + assertEmpty(map); + } + + @Test + public void iterator() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(); + for ( TagMap.Entry entry: map ) { + // makes sure that each key is visited once and only once + assertTrue(keys.add(entry.tag())); + } + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + @Test + public void forEachConsumer() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(size); + map.forEach((entry) -> keys.add(entry.tag())); + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + @Test + public void forEachBiConsumer() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(size); + map.forEach(keys, (k, entry) -> k.add(entry.tag())); + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + @Test + public void forEachTriConsumer() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(size); + map.forEach(keys, "hi", (k, msg, entry) -> keys.add(entry.tag())); + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + static final int randomSize() { + return ThreadLocalRandom.current().nextInt(MANY_SIZE); + } + + static final TagMap createTagMap() { + return createTagMap(randomSize()); + } + + static final TagMap createTagMap(int size) { + TagMap map = new TagMap(); + for ( int i = 0; i < size; ++i ) { + map.set(key(i), value(i)); + } + return map; + } + + static final String key(int i) { + return "key-" + i; + } + + static final String value(int i) { + return "value-" + i; + } + + static final String altValue(int i) { + return "alt-value-" + i; + } + + static final int count(Iterable iterable) { + return count(iterable.iterator()); + } + + static final int count(Iterator iter) { + int count; + for ( count = 0; iter.hasNext(); ++count ) { + iter.next(); + } + return count; + } + + static final void assertEntry(String key, String value, TagMap map) { + TagMap.Entry entry = map.getEntry(key); + assertNotNull(entry); + + assertEquals(key, entry.tag()); + assertEquals(key, entry.getKey()); + + assertEquals(value, entry.objectValue()); + assertTrue(entry.isObject()); + assertEquals(value, entry.getValue()); + + assertEquals(value, entry.stringValue()); + + assertTrue(map.containsKey(key)); + assertTrue(map.keySet().contains(key)); + + assertTrue(map.containsValue(value)); + assertTrue(map.values().contains(value)); + } + + static final void assertSize(int size, TagMap map) { + assertEquals(size, map.computeSize()); + assertEquals(size, map.size()); + + assertEquals(size, count(map)); + assertEquals(size, map.keySet().size()); + assertEquals(size, map.values().size()); + assertEquals(size, count(map.keySet())); + assertEquals(size, count(map.values())); + } + + static final void assertNotEmpty(TagMap map) { + assertFalse(map.checkIfEmpty()); + assertFalse(map.isEmpty()); + } + + static final void assertEmpty(TagMap map) { + assertTrue(map.checkIfEmpty()); + assertTrue(map.isEmpty()); + } + + static final void assertFrozen(TagMap map) { + IllegalStateException ex = null; + try { + map.put("foo", "bar"); + } catch ( IllegalStateException e ) { + ex = e; + } + assertNotNull(ex); + } + + static final void assertFrozen(Runnable runnable) { + IllegalStateException ex = null; + try { + runnable.run(); + } catch ( IllegalStateException e ) { + ex = e; + } + assertNotNull(ex); + } +} From a570c4db23947b0def4362faa14f4c480550f56f Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 11:50:37 -0400 Subject: [PATCH 05/84] spotless --- .../domain/AbstractTestSession.java | 1 - .../trace/civisibility/domain/TestImpl.java | 5 +- .../DefaultExceptionDebuggerTest.java | 1 - .../java/datadog/trace/core/CoreTracer.java | 100 +- .../datadog/trace/core/DDSpanContext.java | 60 +- .../java/datadog/trace/core/Metadata.java | 4 +- .../trace/core/propagation/B3HttpCodec.java | 1 - .../core/propagation/ContextInterpreter.java | 28 +- .../core/propagation/ExtractedContext.java | 15 +- .../core/taginterceptor/TagInterceptor.java | 28 +- .../core/tagprocessor/BaseServiceAdder.java | 3 +- .../tagprocessor/PayloadTagsProcessor.java | 2 +- .../tagprocessor/PeerServiceCalculator.java | 1 - .../core/tagprocessor/PostProcessorChain.java | 5 +- .../core/tagprocessor/QueryObfuscator.java | 4 +- .../tagprocessor/RemoteHostnameAdder.java | 1 - .../tagprocessor/SpanPointersProcessor.java | 5 +- .../core/tagprocessor/TagsPostProcessor.java | 14 +- .../PostProcessorChainTest.groovy | 2 +- .../main/java/datadog/trace/api/Config.java | 4 +- .../main/java/datadog/trace/api/TagMap.java | 1460 ++++++++--------- .../datadog/trace/api/gateway/IGSpanInfo.java | 3 +- .../trace/api/naming/NamingSchema.java | 1 - .../api/naming/v0/PeerServiceNamingV0.java | 5 +- .../api/naming/v1/PeerServiceNamingV1.java | 3 +- .../instrumentation/api/AgentSpan.java | 6 +- .../instrumentation/api/ExtractedSpan.java | 1 - .../instrumentation/api/NoopSpan.java | 5 +- .../instrumentation/api/TagContext.java | 21 +- .../datadog/trace/api/TagMapBuilderTest.java | 54 +- .../datadog/trace/api/TagMapEntryTest.java | 154 +- .../datadog/trace/api/TagMapFuzzTest.java | 1039 ++++++++++-- .../java/datadog/trace/api/TagMapTest.java | 199 ++- 33 files changed, 1995 insertions(+), 1240 deletions(-) diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java index 645d978934a..589a3c1072d 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java @@ -6,7 +6,6 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; import datadog.trace.api.IdGenerationStrategy; -import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric; import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java index c9464a7add0..73cdac7ec6c 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java @@ -6,7 +6,6 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; -import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.DDTest; import datadog.trace.api.civisibility.InstrumentationTestBridge; @@ -45,7 +44,6 @@ import datadog.trace.civisibility.test.ExecutionResults; import java.lang.reflect.Method; import java.util.Collection; -import java.util.Collections; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -101,8 +99,7 @@ public TestImpl( this.context = new TestContextImpl(coverageStore); - AgentSpanContext traceContext = - new TagContext(CIConstants.CIAPP_TEST_ORIGIN, null); + AgentSpanContext traceContext = new TagContext(CIConstants.CIAPP_TEST_ORIGIN, null); AgentTracer.SpanBuilder spanBuilder = AgentTracer.get() .buildSpan(CI_VISIBILITY_INSTRUMENTATION_NAME, testDecorator.component() + ".test") diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java index 3afe5d345a9..02e5a2ad87b 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java @@ -42,7 +42,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; 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 ce7a14f38f6..788dbef8db6 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 @@ -105,7 +105,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -187,12 +186,14 @@ public static CoreTracerBuilder builder() { /** A set of tags that are added only to the application's root span */ private final TagMap localRootSpanTags; + private final boolean localRootSpanTagsNeedIntercept; /** A set of tags that are added to every span */ private final TagMap defaultSpanTags; + private boolean defaultSpanTagsNeedsIntercept; - + /** number of spans in a pending trace before they get flushed */ private final int partialFlushMinSpans; @@ -374,7 +375,7 @@ public CoreTracerBuilder localRootSpanTags(Map localRootSpanTags) { this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); return this; } - + public CoreTracerBuilder localRootSpanTags(TagMap tagMap) { this.localRootSpanTags = tagMap.immutableCopy(); return this; @@ -384,7 +385,7 @@ public CoreTracerBuilder defaultSpanTags(Map defaultSpanTags) { this.defaultSpanTags = TagMap.fromMapImmutable(defaultSpanTags); return this; } - + public CoreTracerBuilder defaultSpanTags(TagMap defaultSpanTags) { this.defaultSpanTags = defaultSpanTags.immutableCopy(); return this; @@ -535,7 +536,7 @@ public CoreTracer build() { flushOnClose); } } - + @Deprecated private CoreTracer( final Config config, @@ -564,33 +565,33 @@ private CoreTracer( final boolean pollForTracingConfiguration, final boolean injectBaggageAsTags, final boolean flushOnClose) { - this( - config, - serviceName, - sharedCommunicationObjects, - writer, - idGenerationStrategy, - sampler, - singleSpanSampler, - injector, - extractor, - TagMap.fromMap(localRootSpanTags), - defaultSpanTags, - serviceNameMappings, - taggedHeaders, - baggageMapping, - partialFlushMinSpans, - statsDClient, - tagInterceptor, - strictTraceWrites, - instrumentationGateway, - timeSource, - dataStreamsMonitoring, - profilingContextIntegration, - pollForTracerFlareRequests, - pollForTracingConfiguration, - injectBaggageAsTags, - flushOnClose); + this( + config, + serviceName, + sharedCommunicationObjects, + writer, + idGenerationStrategy, + sampler, + singleSpanSampler, + injector, + extractor, + TagMap.fromMap(localRootSpanTags), + defaultSpanTags, + serviceNameMappings, + taggedHeaders, + baggageMapping, + partialFlushMinSpans, + statsDClient, + tagInterceptor, + strictTraceWrites, + instrumentationGateway, + timeSource, + dataStreamsMonitoring, + profilingContextIntegration, + pollForTracerFlareRequests, + pollForTracingConfiguration, + injectBaggageAsTags, + flushOnClose); } // These field names must be stable to ensure the builder api is stable. @@ -793,7 +794,7 @@ private CoreTracer( if (config.isDataStreamsEnabled()) { Propagators.register(DSM_CONCERN, this.dataStreamsMonitoring.propagator()); } - + if (config.isCiVisibilityEnabled()) { if (config.isCiVisibilityTraceSanitationEnabled()) { addTraceInterceptor(CiVisibilityTraceInterceptor.INSTANCE); @@ -851,8 +852,9 @@ private CoreTracer( } else { this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); } - - this.localRootSpanTagsNeedIntercept = this.tagInterceptor.needsIntercept(this.localRootSpanTags); + + this.localRootSpanTagsNeedIntercept = + this.tagInterceptor.needsIntercept(this.localRootSpanTags); } /** Used by AgentTestRunner to inject configuration into the test tracer. */ @@ -1368,10 +1370,9 @@ public static class CoreSpanBuilder implements AgentTracer.SpanBuilder { private long spanId; CoreSpanBuilder( - final CoreTracer tracer, + final CoreTracer tracer, final String instrumentationName, - final CharSequence operationName) - { + final CharSequence operationName) { this.instrumentationName = instrumentationName; this.operationName = operationName; this.tracer = tracer; @@ -1489,8 +1490,8 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { } TagMap.Builder tagBuilder = this.tagBuilder; if (tagBuilder == null) { - // Insertion order is important, so using TagBuilder which builds up a set - // of Entry modifications in order + // Insertion order is important, so using TagBuilder which builds up a set + // of Entry modifications in order this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { @@ -1549,7 +1550,7 @@ private DDSpanContext buildSpanContext() { final int samplingPriority; final CharSequence origin; final TagMap coreTags; - final boolean coreTagsNeedsIntercept; + final boolean coreTagsNeedsIntercept; final TagMap rootSpanTags; final boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; @@ -1717,12 +1718,12 @@ private DDSpanContext buildSpanContext() { boolean mergedTracerTagsNeedsIntercept = traceConfig.mergedTracerTagsNeedsIntercept; final int tagsSize = 0; -// final int tagsSize = -// mergedTracerTags.computeSize() -// + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) -// + (null == coreTags ? 0 : coreTags.size()) -// + (null == rootSpanTags ? 0 : rootSpanTags.size()) -// + (null == contextualTags ? 0 : contextualTags.size()); + // final int tagsSize = + // mergedTracerTags.computeSize() + // + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) + // + (null == coreTags ? 0 : coreTags.size()) + // + (null == rootSpanTags ? 0 : rootSpanTags.size()) + // + (null == contextualTags ? 0 : contextualTags.size()); if (builderRequestContextDataAppSec != null) { requestContextDataAppSec = builderRequestContextDataAppSec; @@ -1799,7 +1800,7 @@ protected class ConfigSnapshot extends DynamicConfig.Snapshot { protected ConfigSnapshot( DynamicConfig.Builder builder, ConfigSnapshot oldSnapshot) { super(builder, oldSnapshot); - + if (null == oldSnapshot) { sampler = CoreTracer.this.initialSampler; } else if (Objects.equals(getTraceSampleRate(), oldSnapshot.getTraceSampleRate()) @@ -1817,7 +1818,8 @@ protected ConfigSnapshot( mergedTracerTagsNeedsIntercept = oldSnapshot.mergedTracerTagsNeedsIntercept; } else { mergedTracerTags = withTracerTags(getTracingTags(), CoreTracer.this.initialConfig, this); - mergedTracerTagsNeedsIntercept = CoreTracer.this.tagInterceptor.needsIntercept(mergedTracerTags); + mergedTracerTagsNeedsIntercept = + CoreTracer.this.tagInterceptor.needsIntercept(mergedTracerTags); } } } 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 3349205d444..4efee4c0e6a 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 @@ -8,7 +8,6 @@ import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; import datadog.trace.api.TagMap; - import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; import datadog.trace.api.config.TracerConfig; @@ -343,7 +342,7 @@ public DDSpanContext( this.pathwayContext = pathwayContext; this.unsafeTags = new TagMap(); - + // must set this before setting the service and resource names below this.profilingContextIntegration = profilingContextIntegration; // as fast as we can try to make this operation, we still might need to activate/deactivate @@ -749,42 +748,45 @@ public void setTag(final String tag, final Object value) { } } } - + void setAllTags(final TagMap map, boolean needsIntercept) { - if ( map == null ) { + if (map == null) { return; } - + synchronized (unsafeTags) { - if ( needsIntercept ) { - map.forEach(this, traceCollector.getTracer().getTagInterceptor(), (ctx, tagInterceptor, tagEntry) -> { - String tag = tagEntry.tag(); - Object value = tagEntry.objectValue(); - - if (!tagInterceptor.interceptTag(ctx, tag, value)) { - ctx.unsafeTags.putEntry(tagEntry); - } - }); + if (needsIntercept) { + map.forEach( + this, + traceCollector.getTracer().getTagInterceptor(), + (ctx, tagInterceptor, tagEntry) -> { + String tag = tagEntry.tag(); + Object value = tagEntry.objectValue(); + + if (!tagInterceptor.interceptTag(ctx, tag, value)) { + ctx.unsafeTags.putEntry(tagEntry); + } + }); } else { - unsafeTags.putAll(map); + unsafeTags.putAll(map); } } } - + void setAllTags(final TagMap.Builder builder) { - if ( builder == null ) { + if (builder == null) { return; } - + TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { for (final TagMap.Entry tagEntry : builder) { - if ( tagEntry.isRemoval() ) { + if (tagEntry.isRemoval()) { unsafeTags.removeEntry(tagEntry.tag()); } else { - String tag = tagEntry.tag(); + String tag = tagEntry.tag(); Object value = tagEntry.objectValue(); - + if (!tagInterceptor.interceptTag(this, tag, value)) { unsafeTags.putEntry(tagEntry); } @@ -794,11 +796,11 @@ void setAllTags(final TagMap.Builder builder) { } void setAllTags(final Map map) { - if ( map == null ) { - return; - } else if ( map instanceof TagMap ) { - setAllTags((TagMap)map); - } else if ( !map.isEmpty() ) { + if (map == null) { + return; + } else if (map instanceof TagMap) { + setAllTags((TagMap) map); + } else if (!map.isEmpty()) { TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { for (final Map.Entry tag : map.entrySet()) { @@ -807,7 +809,7 @@ void setAllTags(final Map map) { } } } - } + } } void unsafeSetTag(final String tag, final Object value) { @@ -848,7 +850,7 @@ public Object unsafeGetTag(final String tag) { public TagMap getTags() { synchronized (unsafeTags) { TagMap tags = unsafeTags.copy(); - + tags.put(DDTags.THREAD_ID, threadId); // maintain previously observable type of the thread name :| tags.put(DDTags.THREAD_NAME, threadName.toString()); @@ -896,7 +898,7 @@ public void processTagsAndBaggage( synchronized (unsafeTags) { // Tags TagsPostProcessorFactory.instance().processTags(unsafeTags, this, links); - + String linksTag = DDSpanLink.toTag(links); if (linksTag != null) { unsafeTags.put(SPAN_LINKS, linksTag); 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 08a1f46ca9f..e24aa8286e7 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,11 +2,9 @@ import static datadog.trace.api.sampling.PrioritySampling.UNSET; -import java.util.HashMap; -import java.util.Map; - import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import java.util.Map; public final class Metadata { private final long threadId; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java index f13cabf2ec6..c9af7dec2a2 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java @@ -16,7 +16,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.TreeMap; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java index b8e693a57f9..972c4b6d43e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java @@ -15,10 +15,6 @@ import static datadog.trace.core.propagation.HttpCodec.X_FORWARDED_PROTO_KEY; import static datadog.trace.core.propagation.HttpCodec.X_REAL_IP_KEY; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; - import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; @@ -32,6 +28,9 @@ import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.TagContext; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; /** * When adding new context fields to the ContextInterpreter class remember to clear them in the @@ -78,12 +77,12 @@ protected ContextInterpreter(Config config) { this.propagationTagsFactory = PropagationTags.factory(config); this.requestHeaderTagsCommaAllowed = config.isRequestHeaderTagsCommaAllowed(); } - + final TagMap.Builder tagBuilder() { - if ( tagBuilder == null ) { - tagBuilder = TagMap.builder(); - } - return tagBuilder; + if (tagBuilder == null) { + tagBuilder = TagMap.builder(); + } + return tagBuilder; } /** @@ -198,10 +197,11 @@ protected final boolean handleTags(String key, String value) { final String lowerCaseKey = toLowerCase(key); final String mappedKey = headerTags.get(lowerCaseKey); if (null != mappedKey) { - tagBuilder().put( - mappedKey, - HttpCodec.decode( - requestHeaderTagsCommaAllowed ? value : HttpCodec.firstHeaderValue(value))); + tagBuilder() + .put( + mappedKey, + HttpCodec.decode( + requestHeaderTagsCommaAllowed ? value : HttpCodec.firstHeaderValue(value))); return true; } return false; @@ -230,7 +230,7 @@ public ContextInterpreter reset(TraceConfig traceConfig) { samplingPriority = PrioritySampling.UNSET; origin = null; endToEndStartTime = 0; - if ( tagBuilder != null ) tagBuilder.reset(); + if (tagBuilder != null) tagBuilder.reset(); baggage = Collections.emptyMap(); valid = true; fullContext = true; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java index 07e594adc47..af503e6a6ed 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java @@ -64,7 +64,7 @@ public ExtractedContext( this.endToEndStartTime = endToEndStartTime; this.propagationTags = propagationTags; } - + /* * DQH - kept for testing purposes only */ @@ -81,7 +81,18 @@ public ExtractedContext( final PropagationTags propagationTags, final TraceConfig traceConfig, final TracePropagationStyle propagationStyle) { - this(traceId, spanId, samplingPriority, origin, endToEndStartTime, baggage, tags == null ? null : TagMap.fromMap(tags), httpHeaders, propagationTags, traceConfig, propagationStyle); + this( + traceId, + spanId, + samplingPriority, + origin, + endToEndStartTime, + baggage, + tags == null ? null : TagMap.fromMap(tags), + httpHeaders, + propagationTags, + traceConfig, + propagationStyle); } @Override diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index b0f10a5f3bc..3da653c5398 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -83,21 +83,21 @@ public TagInterceptor( shouldSetUrlResourceAsName = ruleFlags.isEnabled(URL_AS_RESOURCE_NAME); this.jeeSplitByDeployment = jeeSplitByDeployment; } - + public boolean needsIntercept(TagMap map) { - for ( TagMap.Entry entry: map ) { - if ( needsIntercept(entry.tag()) ) return true; - } - return false; + for (TagMap.Entry entry : map) { + if (needsIntercept(entry.tag())) return true; + } + return false; } - + public boolean needsIntercept(Map map) { - for ( String tag: map.keySet() ) { - if ( needsIntercept(tag) ) return true; - } - return false; + for (String tag : map.keySet()) { + if (needsIntercept(tag)) return true; + } + return false; } - + public boolean needsIntercept(String tag) { switch (tag) { case DDTags.RESOURCE_NAME: @@ -120,10 +120,10 @@ public boolean needsIntercept(String tag) { case HTTP_URL: case ORIGIN_KEY: case MEASURED: - return true; - + return true; + default: - return splitServiceTags.contains(tag); + return splitServiceTags.contains(tag); } } 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 5d43edd8046..4a2f2d5377f 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 @@ -5,7 +5,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.core.DDSpanContext; - import java.util.List; import javax.annotation.Nullable; @@ -24,6 +23,6 @@ public void processTags( && !ddService.toString().equalsIgnoreCase(spanContext.getServiceName())) { unsafeTags.put(DDTags.BASE_SERVICE, ddService); unsafeTags.remove("version"); - } + } } } 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 3a63ae7d390..3d413d98b5f 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 @@ -82,7 +82,7 @@ public void processTags( if (unsafeTags.remove(tagPrefix) != null) { spanMaxTags -= 1; } - + PayloadTagsData payloadTagsData = (PayloadTagsData) tagValue; PayloadTagsCollector payloadTagsCollector = new PayloadTagsCollector(maxDepth, spanMaxTags, redactionRules, tagPrefix, unsafeTags); 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 5f979ec8f04..9a4e9377cb9 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 @@ -8,7 +8,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.DDSpanContext; - import java.util.List; import java.util.Map; import javax.annotation.Nonnull; 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 5bef7746540..77374d742cb 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 @@ -3,9 +3,7 @@ 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; @@ -17,7 +15,8 @@ public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { } @Override - public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { for (final TagsPostProcessor tagsPostProcessor : chain) { tagsPostProcessor.processTags(unsafeTags, spanContext, spanLinks); } 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 19fa70cc52f..37bbd470596 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 @@ -10,7 +10,6 @@ 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; @@ -59,7 +58,8 @@ private String obfuscate(String query) { } @Override - public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { Object query = unsafeTags.getObject(DDTags.HTTP_QUERY); if (query instanceof CharSequence) { query = obfuscate(query.toString()); 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 953e8a67387..7bd45cd2c92 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 @@ -4,7 +4,6 @@ import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.core.DDSpanContext; - import java.util.List; import java.util.function.Supplier; 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 a8c9da98da8..28a98671a97 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 @@ -17,7 +17,6 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; -import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +32,7 @@ public final class SpanPointersProcessor extends TagsPostProcessor { @Override public void processTags( TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { - TagMap.Entry eTagEntry = unsafeTags.removeEntry(S3_ETAG); + TagMap.Entry eTagEntry = unsafeTags.removeEntry(S3_ETAG); if (eTagEntry == null) { return; } @@ -43,7 +42,7 @@ public void processTags( LOG.debug("Unable to calculate span pointer hash because could not find bucket or key tags."); return; } - + String eTag = eTagEntry.stringValue(); // Hash calculation rules: 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 4050e9baf30..d0acedb40ee 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 @@ -3,23 +3,19 @@ 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 + * 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; + Map unsafeTags, DDSpanContext context, List links) { + TagMap map = TagMap.fromMap(unsafeTags); + this.processTags(map, context, links); + return map; } public abstract void processTags( 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 cd82cc1a2a6..8961c8d41f8 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 @@ -46,7 +46,7 @@ class PostProcessorChainTest extends DDSpecification { void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { if (unsafeTags.containsKey("test")) { unsafeTags.put("found", "true") - } + } } } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 53029db8f9e..11e2754eb0f 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3469,10 +3469,10 @@ public boolean isApmTracingEnabled() { /** @return A map of tags to be applied only to the local application root span. */ public TagMap getLocalRootSpanTags() { final Map runtimeTags = getRuntimeTags(); - + final TagMap result = new TagMap(); result.putAll(runtimeTags); - + result.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); result.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); result.put(DDTags.PROFILING_ENABLED, isProfilingEnabled() ? 1 : 0); diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index f0839e1aacc..e988106f298 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1,5 +1,6 @@ package datadog.trace.api; +import datadog.trace.api.function.TriConsumer; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; @@ -13,177 +14,167 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import datadog.trace.api.function.TriConsumer; - /** - * A super simple hash map designed for... - * - fast copy from one map to another - * - compatibility with Builder idioms - * - building small maps as fast as possible - * - storing primitives without boxing - * - minimal memory footprint - * - * This is mainly accomplished by using immutable entry objects that can - * reference an object or a primitive. By using immutable entries, - * the entry objects can be shared between builders & maps freely. - * - * This map lacks some features of a regular java.util.Map... - * - Entry object mutation - * - size tracking - falls back to computeSize - * - manipulating Map through the entrySet() or values() - * - * Also lacks features designed for handling large maps... - * - bucket array expansion - * - adaptive collision + * A super simple hash map designed for... - fast copy from one map to another - compatibility with + * Builder idioms - building small maps as fast as possible - storing primitives without boxing - + * minimal memory footprint + * + *

This is mainly accomplished by using immutable entry objects that can reference an object or a + * primitive. By using immutable entries, the entry objects can be shared between builders & maps + * freely. + * + *

This map lacks some features of a regular java.util.Map... - Entry object mutation - size + * tracking - falls back to computeSize - manipulating Map through the entrySet() or values() + * + *

Also lacks features designed for handling large maps... - bucket array expansion - adaptive + * collision */ public final class TagMap implements Map, Iterable { public static final TagMap EMPTY = createEmpty(); - + static final TagMap createEmpty() { return new TagMap().freeze(); } - + public static final Builder builder() { return new Builder(); } - + public static final Builder builder(int size) { return new Builder(size); } - + public static final TagMap fromMap(Map map) { TagMap tagMap = new TagMap(); tagMap.putAll(map); return tagMap; } - + public static final TagMap fromMapImmutable(Map map) { - if ( map.isEmpty() ) { + if (map.isEmpty()) { return TagMap.EMPTY; } else { return fromMap(map).freeze(); } } - + private final Object[] buckets; private boolean frozen; - + public TagMap() { // needs to be a power of 2 for bucket masking calculation to work as intended this.buckets = new Object[1 << 4]; this.frozen = false; } - - /** - * Used for inexpensive immutable - */ + + /** Used for inexpensive immutable */ private TagMap(Object[] buckets) { this.buckets = buckets; this.frozen = true; } - + @Deprecated @Override public final int size() { return this.computeSize(); } - + /** * Computes the size of the TagMap - * - * computeSize is fast but is an O(n) operation. + * + *

computeSize is fast but is an O(n) operation. */ public final int computeSize() { Object[] thisBuckets = this.buckets; - + int size = 0; - for ( int i = 0; i < thisBuckets.length; ++i ) { + for (int i = 0; i < thisBuckets.length; ++i) { Object curBucket = thisBuckets[i]; - - if ( curBucket instanceof Entry ) { + + if (curBucket instanceof Entry) { size += 1; - } else if ( curBucket instanceof BucketGroup ) { - BucketGroup curGroup = (BucketGroup)curBucket; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; size += curGroup.sizeInChain(); } } return size; } - + @Deprecated @Override public boolean isEmpty() { return this.checkIfEmpty(); } - + public final boolean checkIfEmpty() { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object curBucket = thisBuckets[i]; - - if ( curBucket instanceof Entry ) { + + if (curBucket instanceof Entry) { return false; - } else if ( curBucket instanceof BucketGroup ) { - BucketGroup curGroup = (BucketGroup)curBucket; - if ( !curGroup.isEmptyChain() ) return false; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; + if (!curGroup.isEmptyChain()) return false; } } - + return true; } - + @Override public boolean containsKey(Object key) { - if ( !(key instanceof String) ) return false; - - return (this.getEntry((String)key) != null); + if (!(key instanceof String)) return false; + + return (this.getEntry((String) key) != null); } - + @Override public boolean containsValue(Object value) { - for ( Entry entry : this ) { - if ( entry.objectValue().equals(value) ) return true; + for (Entry entry : this) { + if (entry.objectValue().equals(value)) return true; } return false; } - + @Deprecated @Override public Set keySet() { return new Keys(this); } - + @Deprecated @Override public Collection values() { return new Values(this); } - + @Deprecated @Override public Set> entrySet() { return new Entries(this); } - + @Deprecated @Override public final Object get(Object tag) { - if ( !(tag instanceof String) ) return null; - - return this.getObject((String)tag); + if (!(tag instanceof String)) return null; + + return this.getObject((String) tag); } - + public final Object getObject(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.objectValue(); } - + public final String getString(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.stringValue(); } - + public final boolean getBoolean(String tag) { Entry entry = this.getEntry(tag); return entry == null ? false : entry.booleanValue(); @@ -193,22 +184,22 @@ public final int getInt(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0 : entry.intValue(); } - + public final long getLong(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0L : entry.longValue(); } - + public final float getFloat(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0F : entry.floatValue(); } - + public final double getDouble(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0D : entry.doubleValue(); } - + public final Entry getEntry(String tag) { Object[] thisBuckets = this.buckets; @@ -216,180 +207,181 @@ public final Entry getEntry(String tag) { int bucketIndex = hash & (thisBuckets.length - 1); Object bucket = thisBuckets[bucketIndex]; - if ( bucket == null ) { + if (bucket == null) { return null; - } else if ( bucket instanceof Entry ) { - Entry tagEntry = (Entry)bucket; - if ( tagEntry.matches(tag) ) return tagEntry; - } else if ( bucket instanceof BucketGroup ) { - BucketGroup lastGroup = (BucketGroup)bucket; - + } else if (bucket instanceof Entry) { + Entry tagEntry = (Entry) bucket; + if (tagEntry.matches(tag)) return tagEntry; + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; + Entry tagEntry = lastGroup.findInChain(hash, tag); - if ( tagEntry != null ) return tagEntry; + if (tagEntry != null) return tagEntry; } return null; } - + @Deprecated public final Object put(String tag, Object value) { TagMap.Entry entry = this.putEntry(Entry.newAnyEntry(tag, value)); return entry == null ? null : entry.objectValue(); } - + public final Entry set(String tag, Object value) { return this.putEntry(Entry.newAnyEntry(tag, value)); } - + public final Entry set(String tag, CharSequence value) { return this.putEntry(Entry.newObjectEntry(tag, value)); } - + public final Entry set(String tag, boolean value) { return this.putEntry(Entry.newBooleanEntry(tag, value)); - } - + } + public final Entry set(String tag, int value) { return this.putEntry(Entry.newIntEntry(tag, value)); } - + public final Entry set(String tag, long value) { return this.putEntry(Entry.newLongEntry(tag, value)); } - + public final Entry set(String tag, float value) { return this.putEntry(Entry.newFloatEntry(tag, value)); } - + public final Entry set(String tag, double value) { return this.putEntry(Entry.newDoubleEntry(tag, value)); } - + public final Entry putEntry(Entry newEntry) { this.checkWriteAccess(); - + Object[] thisBuckets = this.buckets; int newHash = newEntry.hash(); int bucketIndex = newHash & (thisBuckets.length - 1); - + Object bucket = thisBuckets[bucketIndex]; - if ( bucket == null ) { + if (bucket == null) { thisBuckets[bucketIndex] = newEntry; - + return null; - } else if ( bucket instanceof Entry ) { - Entry existingEntry = (Entry)bucket; - if ( existingEntry.matches(newEntry.tag) ) { + } else if (bucket instanceof Entry) { + Entry existingEntry = (Entry) bucket; + if (existingEntry.matches(newEntry.tag)) { thisBuckets[bucketIndex] = newEntry; - + return existingEntry; } else { - thisBuckets[bucketIndex] = new BucketGroup( - existingEntry.hash(), existingEntry, - newHash, newEntry); - + thisBuckets[bucketIndex] = + new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry); + return null; } - } else if ( bucket instanceof BucketGroup ){ - BucketGroup lastGroup = (BucketGroup)bucket; - + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag); - if ( containingGroup != null ) { + if (containingGroup != null) { return containingGroup._replace(newHash, newEntry); } - - if ( !lastGroup.insertInChain(newHash, newEntry) ) { + + if (!lastGroup.insertInChain(newHash, newEntry)) { thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup); } return null; } return null; } - + public final void putAll(Iterable entries) { this.checkWriteAccess(); - - for ( Entry tagEntry: entries ) { - if ( tagEntry.isRemoval() ) { + + for (Entry tagEntry : entries) { + if (tagEntry.isRemoval()) { this.remove(tagEntry.tag); } else { this.putEntry(tagEntry); } } } - + public final void putAll(TagMap.Builder builder) { putAll(builder.entries, builder.nextPos); } - + private final void putAll(Entry[] tagEntries, int size) { - for ( int i = 0; i < size && i < tagEntries.length; ++i ) { + for (int i = 0; i < size && i < tagEntries.length; ++i) { Entry tagEntry = tagEntries[i]; - - if ( tagEntry.isRemoval() ) { + + if (tagEntry.isRemoval()) { this.remove(tagEntry.tag); } else { this.putEntry(tagEntry); } } } - + public final void putAll(TagMap that) { this.checkWriteAccess(); - + Object[] thisBuckets = this.buckets; Object[] thatBuckets = that.buckets; // Since TagMap-s don't support expansion, buckets are perfectly aligned - for ( int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i ) { + for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { Object thatBucket = thatBuckets[i]; // nothing in the other hash, just skip this bucket - if ( thatBucket == null ) continue; + if (thatBucket == null) continue; Object thisBucket = thisBuckets[i]; - if ( thisBucket == null ) { + if (thisBucket == null) { // This bucket is null, easy case // Either copy over the sole entry or clone the BucketGroup chain - if ( thatBucket instanceof Entry ) { + if (thatBucket instanceof Entry) { thisBuckets[i] = thatBucket; - } else if ( thatBucket instanceof BucketGroup ) { - BucketGroup thatGroup = (BucketGroup)thatBucket; - + } else if (thatBucket instanceof BucketGroup) { + BucketGroup thatGroup = (BucketGroup) thatBucket; + thisBuckets[i] = thatGroup.cloneChain(); } - } else if ( thisBucket instanceof Entry ) { + } else if (thisBucket instanceof Entry) { // This bucket is a single entry, medium complexity case // If other side is an Entry - just merge the entries into a bucket - // If other side is a BucketGroup - then clone the group and insert the entry normally into the cloned group - - Entry thisEntry = (Entry)thisBucket; + // If other side is a BucketGroup - then clone the group and insert the entry normally into + // the cloned group + + Entry thisEntry = (Entry) thisBucket; int thisHash = thisEntry.hash(); - - if ( thatBucket instanceof Entry ) { - Entry thatEntry = (Entry)thatBucket; + + if (thatBucket instanceof Entry) { + Entry thatEntry = (Entry) thatBucket; int thatHash = thatEntry.hash(); - - if ( thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { + + if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { thisBuckets[i] = thatEntry; } else { - thisBuckets[i] = new BucketGroup( - thisHash, thisEntry, - thatHash, thatEntry); + thisBuckets[i] = + new BucketGroup( + thisHash, thisEntry, + thatHash, thatEntry); } - } else if ( thatBucket instanceof BucketGroup ) { - BucketGroup thatGroup = (BucketGroup)thatBucket; - + } else if (thatBucket instanceof BucketGroup) { + BucketGroup thatGroup = (BucketGroup) thatBucket; + // Clone the other group, then place this entry into that group BucketGroup thisNewGroup = thatGroup.cloneChain(); - + Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag()); - if ( incomingEntry != null ) { + if (incomingEntry != null) { // there's already an entry w/ the same tag from the incoming TagMap - done thisBuckets[i] = thisNewGroup; - } else if ( thisNewGroup.insertInChain(thisHash, thisEntry) ) { + } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) { // able to add thisEntry into the existing groups thisBuckets[i] = thisNewGroup; } else { @@ -397,293 +389,293 @@ public final void putAll(TagMap that) { thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); } } - } else if ( thisBucket instanceof BucketGroup ) { + } else if (thisBucket instanceof BucketGroup) { // This bucket is a BucketGroup, medium to hard case // If the other side is an entry, just normal insertion procedure - no cloning required - BucketGroup thisGroup = (BucketGroup)thisBucket; - - if ( thatBucket instanceof Entry ) { - Entry thatEntry = (Entry)thatBucket; + BucketGroup thisGroup = (BucketGroup) thisBucket; + + if (thatBucket instanceof Entry) { + Entry thatEntry = (Entry) thatBucket; int thatHash = thatEntry.hash(); - - if ( !thisGroup.replaceOrInsertInChain(thatHash, thatEntry) ) { + + if (!thisGroup.replaceOrInsertInChain(thatHash, thatEntry)) { thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup); } - } else if ( thatBucket instanceof BucketGroup ) { + } else if (thatBucket instanceof BucketGroup) { // Most complicated case - need to walk that bucket group chain and update this chain - BucketGroup thatGroup = (BucketGroup)thatBucket; - + BucketGroup thatGroup = (BucketGroup) thatBucket; + thisBuckets[i] = thisGroup.replaceOrInsertAllInChain(thatGroup); } } } } - + public final void putAll(Map map) { this.checkWriteAccess(); - - if ( map instanceof TagMap ) { - this.putAll((TagMap)map); + + if (map instanceof TagMap) { + this.putAll((TagMap) map); } else { - for ( Map.Entry entry: map.entrySet() ) { + for (Map.Entry entry : map.entrySet()) { this.put(entry.getKey(), entry.getValue()); } } } - + public final void fillMap(Map map) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + map.put(thisEntry.tag, thisEntry.objectValue()); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.fillMapFromChain(map); } } } - + public final void fillStringMap(Map stringMap) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + stringMap.put(thisEntry.tag, thisEntry.stringValue()); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.fillStringMapFromChain(stringMap); } } } - + @Deprecated @Override public final Object remove(Object tag) { - if ( !(tag instanceof String) ) return null; - - Entry entry = this.removeEntry((String)tag); + if (!(tag instanceof String)) return null; + + Entry entry = this.removeEntry((String) tag); return entry == null ? null : entry.objectValue(); } - + public final Entry removeEntry(String tag) { this.checkWriteAccess(); - + Object[] thisBuckets = this.buckets; int hash = _hash(tag); int bucketIndex = hash & (thisBuckets.length - 1); - + Object bucket = thisBuckets[bucketIndex]; // null bucket case - do nothing - if ( bucket instanceof Entry ) { - Entry existingEntry = (Entry)bucket; + if (bucket instanceof Entry) { + Entry existingEntry = (Entry) bucket; if (existingEntry.matches(tag)) { thisBuckets[bucketIndex] = null; return existingEntry; } else { return null; } - } else if ( bucket instanceof BucketGroup) { - BucketGroup lastGroup = (BucketGroup)bucket; - + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag); - if ( containingGroup == null ) { + if (containingGroup == null) { return null; } - + Entry existingEntry = containingGroup._remove(hash, tag); - if ( containingGroup._isEmpty() ) { + if (containingGroup._isEmpty()) { this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup); } - + return existingEntry; } return null; } - + public final TagMap copy() { TagMap copy = new TagMap(); copy.putAll(this); return copy; } - + public final TagMap immutableCopy() { - if ( this.frozen ) { + if (this.frozen) { return this; } else { return this.copy().freeze(); } } - + public final TagMap immutable() { // specialized constructor, freezes map immediately return new TagMap(this.buckets); } - + @Override public final Iterator iterator() { return new EntryIterator(this); } - + public final Stream stream() { - return StreamSupport.stream(spliterator(), false); + return StreamSupport.stream(spliterator(), false); } - + public final void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + consumer.accept(thisEntry); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.forEachInChain(consumer); } } } - + public final void forEach(T thisObj, BiConsumer consumer) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + consumer.accept(thisObj, thisEntry); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.forEachInChain(thisObj, consumer); } } } - - public final void forEach(T thisObj, U otherObj, TriConsumer consumer) { + + public final void forEach( + T thisObj, U otherObj, TriConsumer consumer) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + consumer.accept(thisObj, otherObj, thisEntry); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.forEachInChain(thisObj, otherObj, consumer); } } } - + public final void clear() { this.checkWriteAccess(); - + Arrays.fill(this.buckets, null); } - + public final TagMap freeze() { this.frozen = true; - + return this; } - + public boolean isFrozen() { - return this.frozen; - } - -// final void check() { -// Object[] thisBuckets = this.buckets; -// -// for ( int i = 0; i < thisBuckets.length; ++i ) { -// Object thisBucket = thisBuckets[i]; -// -// if ( thisBucket instanceof Entry ) { -// Entry thisEntry = (Entry)thisBucket; -// int thisHash = thisEntry.hash(); -// -// int expectedBucket = thisHash & (thisBuckets.length - 1); -// assert expectedBucket == i; -// } else if ( thisBucket instanceof BucketGroup ) { -// BucketGroup thisGroup = (BucketGroup)thisBucket; -// -// for ( BucketGroup curGroup = thisGroup; -// curGroup != null; -// curGroup = curGroup.prev ) -// { -// for ( int j = 0; j < BucketGroup.LEN; ++j ) { -// Entry thisEntry = curGroup._entryAt(i); -// if ( thisEntry == null ) continue; -// -// int thisHash = thisEntry.hash(); -// assert curGroup._hashAt(i) == thisHash; -// -// int expectedBucket = thisHash & (thisBuckets.length - 1); -// assert expectedBucket == i; -// } -// } -// } -// } -// } - + return this.frozen; + } + + // final void check() { + // Object[] thisBuckets = this.buckets; + // + // for ( int i = 0; i < thisBuckets.length; ++i ) { + // Object thisBucket = thisBuckets[i]; + // + // if ( thisBucket instanceof Entry ) { + // Entry thisEntry = (Entry)thisBucket; + // int thisHash = thisEntry.hash(); + // + // int expectedBucket = thisHash & (thisBuckets.length - 1); + // assert expectedBucket == i; + // } else if ( thisBucket instanceof BucketGroup ) { + // BucketGroup thisGroup = (BucketGroup)thisBucket; + // + // for ( BucketGroup curGroup = thisGroup; + // curGroup != null; + // curGroup = curGroup.prev ) + // { + // for ( int j = 0; j < BucketGroup.LEN; ++j ) { + // Entry thisEntry = curGroup._entryAt(i); + // if ( thisEntry == null ) continue; + // + // int thisHash = thisEntry.hash(); + // assert curGroup._hashAt(i) == thisHash; + // + // int expectedBucket = thisHash & (thisBuckets.length - 1); + // assert expectedBucket == i; + // } + // } + // } + // } + // } + @Override public String toString() { return toInternalString(); } - + String toPrettyString() { boolean first = true; - + StringBuilder builder = new StringBuilder(128); builder.append('{'); - for ( Entry entry: this ) { - if ( first ) { + for (Entry entry : this) { + if (first) { first = false; } else { builder.append(","); } - + builder.append(entry.tag).append('=').append(entry.stringValue()); } builder.append('}'); return builder.toString(); } - + String toInternalString() { Object[] thisBuckets = this.buckets; - + StringBuilder builder = new StringBuilder(128); - for ( int i = 0; i < thisBuckets.length; ++i ) { + for (int i = 0; i < thisBuckets.length; ++i) { builder.append('[').append(i).append("] = "); - + Object thisBucket = thisBuckets[i]; - if ( thisBucket == null ) { + if (thisBucket == null) { builder.append("null"); - } else if ( thisBucket instanceof Entry ) { + } else if (thisBucket instanceof Entry) { builder.append('{').append(thisBucket).append('}'); - } else if ( thisBucket instanceof BucketGroup ) { - for ( BucketGroup curGroup = (BucketGroup)thisBucket; - curGroup != null; - curGroup = curGroup.prev ) - { + } else if (thisBucket instanceof BucketGroup) { + for (BucketGroup curGroup = (BucketGroup) thisBucket; + curGroup != null; + curGroup = curGroup.prev) { builder.append(curGroup).append(" -> "); } } @@ -691,16 +683,16 @@ String toInternalString() { } return builder.toString(); } - + public final void checkWriteAccess() { - if ( this.frozen ) throw new IllegalStateException("TagMap frozen"); + if (this.frozen) throw new IllegalStateException("TagMap frozen"); } - + static final int _hash(String tag) { int hash = tag.hashCode(); return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); } - + public static final class Entry implements Map.Entry { /* * Special value used to record removals in the builder @@ -714,7 +706,7 @@ public static final class Entry implements Map.Entry { */ public static final byte ANY = 0; public static final byte OBJECT = 1; - + /* * Non-numeric primitive types */ @@ -731,26 +723,25 @@ public static final class Entry implements Map.Entry { public static final byte FLOAT = 8; public static final byte DOUBLE = 9; - static final Entry newAnyEntry(String tag, Object value) { - // DQH - To keep entry creation (e.g. map changes) as fast as possible, + // DQH - To keep entry creation (e.g. map changes) as fast as possible, // the entry construction is kept as simple as possible. - - // Prior versions of this code did type detection on value to - // recognize box types but that proved expensive. So now, - // the type is recorded as an ANY which is an indicator to do + + // Prior versions of this code did type detection on value to + // recognize box types but that proved expensive. So now, + // the type is recorded as an ANY which is an indicator to do // type detection later if need be. return new Entry(tag, ANY, 0L, value); } static final Entry newObjectEntry(String tag, Object value) { return new Entry(tag, OBJECT, 0, value); - } + } static final Entry newBooleanEntry(String tag, boolean value) { return new Entry(tag, BOOLEAN, boolean2Prim(value), Boolean.valueOf(value)); } - + static final Entry newBooleanEntry(String tag, Boolean box) { return new Entry(tag, BOOLEAN, boolean2Prim(box.booleanValue()), box); } @@ -758,7 +749,7 @@ static final Entry newBooleanEntry(String tag, Boolean box) { static final Entry newIntEntry(String tag, int value) { return new Entry(tag, INT, int2Prim(value), null); } - + static final Entry newIntEntry(String tag, Integer box) { return new Entry(tag, INT, int2Prim(box.intValue()), box); } @@ -766,7 +757,7 @@ static final Entry newIntEntry(String tag, Integer box) { static final Entry newLongEntry(String tag, long value) { return new Entry(tag, LONG, long2Prim(value), null); } - + static final Entry newLongEntry(String tag, Long box) { return new Entry(tag, LONG, long2Prim(box.longValue()), box); } @@ -774,7 +765,7 @@ static final Entry newLongEntry(String tag, Long box) { static final Entry newFloatEntry(String tag, float value) { return new Entry(tag, FLOAT, float2Prim(value), null); } - + static final Entry newFloatEntry(String tag, Float box) { return new Entry(tag, FLOAT, float2Prim(box.floatValue()), box); } @@ -782,7 +773,7 @@ static final Entry newFloatEntry(String tag, Float box) { static final Entry newDoubleEntry(String tag, double value) { return new Entry(tag, DOUBLE, double2Prim(value), null); } - + static final Entry newDoubleEntry(String tag, Double box) { return new Entry(tag, DOUBLE, double2Prim(box.doubleValue()), box); } @@ -790,24 +781,26 @@ static final Entry newDoubleEntry(String tag, Double box) { static final Entry newRemovalEntry(String tag) { return new Entry(tag, REMOVED, 0, null); } - + final String tag; int hash; // To optimize construction of Entry around boxed primitives and Object entries, // no type checks are done during construction. - // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into obj - // If an ANY entry is later type checked or request as a primitive, then the ANY will be resolved + // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into + // obj + // If an ANY entry is later type checked or request as a primitive, then the ANY will be + // resolved // to the correct type. - + // From the outside perspective, this object remains functionally immutable. // However, internally, it is important to remember that this type must be thread safe. // That includes multiple threads racing to resolve an ANY entry at the same time. - + volatile byte type; volatile long prim; volatile Object obj; - + volatile String strCache = null; private Entry(String tag, byte type, long prim, Object obj) { @@ -821,11 +814,11 @@ private Entry(String tag, byte type, long prim, Object obj) { public final String tag() { return this.tag; } - + int hash() { int hash = this.hash; - if ( hash != 0 ) return hash; - + if (hash != 0) return hash; + hash = _hash(this.tag); this.hash = hash; return hash; @@ -837,9 +830,9 @@ public final byte type() { public final boolean is(byte type) { byte curType = this.type; - if ( curType == type ) { + if (curType == type) { return true; - } else if ( curType != ANY ) { + } else if (curType != ANY) { return false; } else { return (this.resolveAny() == type); @@ -848,32 +841,32 @@ public final boolean is(byte type) { public final boolean isNumericPrimitive() { byte curType = this.type; - if ( _isNumericPrimitive(curType) ) { + if (_isNumericPrimitive(curType)) { return true; - } else if ( curType != ANY ) { + } else if (curType != ANY) { return false; } else { return _isNumericPrimitive(this.resolveAny()); } } - + public final boolean isNumber() { byte curType = this.type; return _isNumericPrimitive(curType) || (this.obj instanceof Number); } - + private static final boolean _isNumericPrimitive(byte type) { return (type >= BYTE); } - + private final byte resolveAny() { byte curType = this.type; - if ( curType != ANY ) return curType; - + if (curType != ANY) return curType; + Object value = this.obj; long prim; byte resolvedType; - + if (value instanceof Boolean) { Boolean boolValue = (Boolean) value; prim = boolean2Prim(boolValue); @@ -898,16 +891,16 @@ private final byte resolveAny() { prim = 0; resolvedType = OBJECT; } - + this._setPrim(resolvedType, prim); - + return resolvedType; } - + private void _setPrim(byte type, long prim) { // Order is important here, the contract is that prim must be set properly *before* // type is set to a non-object type - + this.prim = prim; this.type = type; } @@ -919,39 +912,39 @@ public final boolean isObject() { public final boolean isRemoval() { return this.is(REMOVED); } - + public final boolean matches(String tag) { return this.tag.equals(tag); } public final Object objectValue() { - if ( this.obj != null ) { + if (this.obj != null) { return this.obj; } // This code doesn't need to handle ANY-s. // An entry that starts as an ANY will always have this.obj set switch (this.type) { - case BOOLEAN: - this.obj = prim2Boolean(this.prim); - break; + case BOOLEAN: + this.obj = prim2Boolean(this.prim); + break; - case INT: - // Maybe use a wider cache that handles response code??? - this.obj = prim2Int(this.prim); - break; + case INT: + // Maybe use a wider cache that handles response code??? + this.obj = prim2Int(this.prim); + break; - case LONG: - this.obj = prim2Long(this.prim); - break; + case LONG: + this.obj = prim2Long(this.prim); + break; - case FLOAT: - this.obj = prim2Float(this.prim); - break; + case FLOAT: + this.obj = prim2Float(this.prim); + break; - case DOUBLE: - this.obj = prim2Double(this.prim); - break; + case DOUBLE: + this.obj = prim2Double(this.prim); + break; } if (this.is(REMOVED)) { @@ -963,37 +956,37 @@ public final Object objectValue() { public final boolean booleanValue() { byte type = this.type; - + if (type == BOOLEAN) { return prim2Boolean(this.prim); } else if (type == ANY && this.obj instanceof Boolean) { - boolean boolValue = (Boolean)this.obj; + boolean boolValue = (Boolean) this.obj; this._setPrim(BOOLEAN, boolean2Prim(boolValue)); return boolValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case INT: - return prim2Int(prim) != 0; + case INT: + return prim2Int(prim) != 0; - case LONG: - return prim2Long(prim) != 0L; + case LONG: + return prim2Long(prim) != 0L; - case FLOAT: - return prim2Float(prim) != 0F; + case FLOAT: + return prim2Float(prim) != 0F; - case DOUBLE: - return prim2Double(prim) != 0D; + case DOUBLE: + return prim2Double(prim) != 0D; - case OBJECT: - return (this.obj != null); + case OBJECT: + return (this.obj != null); - case REMOVED: - return false; + case REMOVED: + return false; } return false; @@ -1001,37 +994,37 @@ public final boolean booleanValue() { public final int intValue() { byte type = this.type; - + if (type == INT) { return prim2Int(this.prim); } else if (type == ANY && this.obj instanceof Integer) { - int intValue = (Integer)this.obj; + int intValue = (Integer) this.obj; this._setPrim(INT, int2Prim(intValue)); return intValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1 : 0; + case BOOLEAN: + return prim2Boolean(prim) ? 1 : 0; - case LONG: - return (int) prim2Long(prim); + case LONG: + return (int) prim2Long(prim); - case FLOAT: - return (int) prim2Float(prim); + case FLOAT: + return (int) prim2Float(prim); - case DOUBLE: - return (int) prim2Double(prim); + case DOUBLE: + return (int) prim2Double(prim); - case OBJECT: - return 0; + case OBJECT: + return 0; - case REMOVED: - return 0; + case REMOVED: + return 0; } return 0; @@ -1039,15 +1032,15 @@ public final int intValue() { public final long longValue() { byte type = this.type; - + if (type == LONG) { return prim2Long(this.prim); } else if (type == ANY && this.obj instanceof Long) { - long longValue = (Long)this.obj; + long longValue = (Long) this.obj; this._setPrim(LONG, long2Prim(longValue)); return longValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; @@ -1077,37 +1070,37 @@ public final long longValue() { public final float floatValue() { byte type = this.type; - + if (type == FLOAT) { return prim2Float(this.prim); } else if (type == ANY && this.obj instanceof Float) { - float floatValue = (Float)this.obj; + float floatValue = (Float) this.obj; this._setPrim(FLOAT, float2Prim(floatValue)); return floatValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1F : 0F; + case BOOLEAN: + return prim2Boolean(prim) ? 1F : 0F; - case INT: - return (float) prim2Int(prim); + case INT: + return (float) prim2Int(prim); - case LONG: - return (float) prim2Long(prim); + case LONG: + return (float) prim2Long(prim); - case DOUBLE: - return (float) prim2Double(prim); + case DOUBLE: + return (float) prim2Double(prim); - case OBJECT: - return 0F; + case OBJECT: + return 0F; - case REMOVED: - return 0F; + case REMOVED: + return 0F; } return 0F; @@ -1115,37 +1108,37 @@ public final float floatValue() { public final double doubleValue() { byte type = this.type; - + if (type == DOUBLE) { return prim2Double(this.prim); } else if (type == ANY && this.obj instanceof Double) { - double doubleValue = (Double)this.obj; + double doubleValue = (Double) this.obj; this._setPrim(DOUBLE, double2Prim(doubleValue)); return doubleValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1D : 0D; + case BOOLEAN: + return prim2Boolean(prim) ? 1D : 0D; - case INT: - return (double) prim2Int(prim); + case INT: + return (double) prim2Int(prim); - case LONG: - return (double) prim2Long(prim); + case LONG: + return (double) prim2Long(prim); - case FLOAT: - return (double) prim2Float(prim); + case FLOAT: + return (double) prim2Float(prim); - case OBJECT: - return 0D; + case OBJECT: + return 0D; - case REMOVED: - return 0D; + case REMOVED: + return 0D; } return 0D; @@ -1153,62 +1146,62 @@ public final double doubleValue() { public final String stringValue() { String strCache = this.strCache; - if ( strCache != null ) { - return strCache; + if (strCache != null) { + return strCache; } - + String computeStr = this.computeStringValue(); this.strCache = computeStr; return computeStr; } - + private final String computeStringValue() { - // Could do type resolution here, + // Could do type resolution here, // but decided to just fallback to this.obj.toString() for ANY case switch (this.type) { - case BOOLEAN: - return Boolean.toString(prim2Boolean(this.prim)); + case BOOLEAN: + return Boolean.toString(prim2Boolean(this.prim)); - case INT: - return Integer.toString(prim2Int(this.prim)); + case INT: + return Integer.toString(prim2Int(this.prim)); - case LONG: - return Long.toString(prim2Long(this.prim)); + case LONG: + return Long.toString(prim2Long(this.prim)); - case FLOAT: - return Float.toString(prim2Float(this.prim)); + case FLOAT: + return Float.toString(prim2Float(this.prim)); - case DOUBLE: - return Double.toString(prim2Double(this.prim)); + case DOUBLE: + return Double.toString(prim2Double(this.prim)); - case REMOVED: - return null; + case REMOVED: + return null; - case OBJECT: - case ANY: - return this.obj.toString(); + case OBJECT: + case ANY: + return this.obj.toString(); } return null; } - + @Override public final String toString() { return this.tag() + '=' + this.stringValue(); } - + @Deprecated @Override public String getKey() { return this.tag(); } - + @Deprecated @Override public Object getValue() { return this.objectValue(); } - + @Deprecated @Override public Object setValue(Object value) { @@ -1255,531 +1248,537 @@ private static final double prim2Double(long prim) { return Double.longBitsToDouble(prim); } } - + public static final class Builder implements Iterable { private Entry[] entries; private int nextPos = 0; - + private Builder() { this(8); } - + private Builder(int size) { this.entries = new Entry[size]; } - + public final boolean isDefinitelyEmpty() { return (this.nextPos == 0); } - + /** - * Provides the estimated size of the map created by the builder - * Doesn't account for overwritten entries or entry removal + * Provides the estimated size of the map created by the builder Doesn't account for overwritten + * entries or entry removal + * * @return */ public final int estimateSize() { return this.nextPos; } - + public final Builder put(String tag, Object value) { return this.put(Entry.newAnyEntry(tag, value)); } - + public final Builder put(String tag, CharSequence value) { return this.put(Entry.newObjectEntry(tag, value)); } - + public final Builder put(String tag, boolean value) { return this.put(Entry.newBooleanEntry(tag, value)); } - + public final Builder put(String tag, int value) { return this.put(Entry.newIntEntry(tag, value)); } - + public final Builder put(String tag, long value) { return this.put(Entry.newLongEntry(tag, value)); } - + public final Builder put(String tag, float value) { return this.put(Entry.newFloatEntry(tag, value)); } - + public final Builder put(String tag, double value) { return this.put(Entry.newDoubleEntry(tag, value)); } - + public final Builder remove(String tag) { return this.put(Entry.newRemovalEntry(tag)); } - + public final Builder put(Entry entry) { - if ( this.nextPos >= this.entries.length ) { + if (this.nextPos >= this.entries.length) { this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); } - + this.entries[this.nextPos++] = entry; return this; } - + public final void reset() { Arrays.fill(this.entries, null); this.nextPos = 0; } - + @Override public final Iterator iterator() { return new BuilderIterator(this.entries, this.nextPos); } - + public TagMap build() { TagMap map = new TagMap(); - if ( this.nextPos != 0 ) map.putAll(this.entries, this.nextPos); + if (this.nextPos != 0) map.putAll(this.entries, this.nextPos); return map; } - + public TagMap buildImmutable() { - if ( this.nextPos == 0 ) { + if (this.nextPos == 0) { return TagMap.EMPTY; } else { return this.build().freeze(); } } } - + private static final class BuilderIterator implements Iterator { private final Entry[] entries; private final int size; - + private int pos; - + BuilderIterator(Entry[] entries, int size) { this.entries = entries; this.size = size; - + this.pos = -1; } - + @Override public final boolean hasNext() { - return ( this.pos + 1 < this.size ); + return (this.pos + 1 < this.size); } - + @Override public Entry next() { - if ( !this.hasNext() ) throw new NoSuchElementException("no next"); - + if (!this.hasNext()) throw new NoSuchElementException("no next"); + return this.entries[++this.pos]; } } - - private static abstract class MapIterator implements Iterator { + + private abstract static class MapIterator implements Iterator { private final Object[] buckets; - + private Entry nextEntry; private int bucketIndex = -1; - + private BucketGroup group = null; private int groupIndex = 0; - + MapIterator(TagMap map) { this.buckets = map.buckets; } - + @Override public boolean hasNext() { - if ( this.nextEntry != null ) return true; - - while ( this.bucketIndex < this.buckets.length ) { + if (this.nextEntry != null) return true; + + while (this.bucketIndex < this.buckets.length) { this.nextEntry = this.advance(); - if ( this.nextEntry != null ) return true; + if (this.nextEntry != null) return true; } - + return false; } - + Entry nextEntry() { - if ( this.nextEntry != null ) { + if (this.nextEntry != null) { Entry nextEntry = this.nextEntry; this.nextEntry = null; return nextEntry; } - - if ( this.hasNext() ) { + + if (this.hasNext()) { return this.nextEntry; } else { throw new NoSuchElementException(); } } - + private final Entry advance() { - while ( this.bucketIndex < this.buckets.length ) { - if ( this.group != null ) { - for ( ++this.groupIndex; this.groupIndex < BucketGroup.LEN; ++this.groupIndex ) { + while (this.bucketIndex < this.buckets.length) { + if (this.group != null) { + for (++this.groupIndex; this.groupIndex < BucketGroup.LEN; ++this.groupIndex) { Entry tagEntry = this.group._entryAt(this.groupIndex); - if ( tagEntry != null ) return tagEntry; + if (tagEntry != null) return tagEntry; } - + // done processing - that group, go to next group this.group = this.group.prev; this.groupIndex = -1; } // if the group is null, then we've finished the current bucket - so advance the bucket - if ( this.group == null ) { - for ( ++this.bucketIndex; this.bucketIndex < this.buckets.length; ++this.bucketIndex ) { + if (this.group == null) { + for (++this.bucketIndex; this.bucketIndex < this.buckets.length; ++this.bucketIndex) { Object bucket = this.buckets[this.bucketIndex]; - - if ( bucket instanceof Entry ) { - return (Entry)bucket; - } else if ( bucket instanceof BucketGroup ) { - this.group = (BucketGroup)bucket; + + if (bucket instanceof Entry) { + return (Entry) bucket; + } else if (bucket instanceof BucketGroup) { + this.group = (BucketGroup) bucket; this.groupIndex = -1; - + break; } } } - }; - + } + ; + return null; } } - + static final class EntryIterator extends MapIterator { EntryIterator(TagMap map) { super(map); } - + @Override public Entry next() { return this.nextEntry(); } } - + /** - * BucketGroup is compromise for performance over a linked list or array - * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities - * - array - wouldn't be able to store hashes close together - * - parallel arrays (one for hashes & another for entries) would require more allocation + * BucketGroup is compromise for performance over a linked list or array - linked list - would + * prevent TagEntry-s from being immutable and would limit sharing opportunities - array - + * wouldn't be able to store hashes close together - parallel arrays (one for hashes & another for + * entries) would require more allocation */ static final class BucketGroup { static final int LEN = 4; - + // int hashFilter = 0; - + // want the hashes together in the same cache line int hash0 = 0; int hash1 = 0; int hash2 = 0; int hash3 = 0; - + Entry entry0 = null; Entry entry1 = null; Entry entry2 = null; Entry entry3 = null; - + BucketGroup prev = null; - + BucketGroup() {} - - /** - * New group with an entry pointing to existing BucketGroup - */ + + /** New group with an entry pointing to existing BucketGroup */ BucketGroup(int hash0, Entry entry0, BucketGroup prev) { this.hash0 = hash0; this.entry0 = entry0; - + this.prev = prev; - + // this.hashFilter = hash0; } - - /** - * New group composed of two entries - */ + + /** New group composed of two entries */ BucketGroup(int hash0, Entry entry0, int hash1, Entry entry1) { this.hash0 = hash0; this.entry0 = entry0; - + this.hash1 = hash1; this.entry1 = entry1; - + // this.hashFilter = hash0 | hash1; } - - /** - * New group composed of 4 entries - used for cloning - */ + + /** New group composed of 4 entries - used for cloning */ BucketGroup( - int hash0, Entry entry0, - int hash1, Entry entry1, - int hash2, Entry entry2, - int hash3, Entry entry3) - { + int hash0, + Entry entry0, + int hash1, + Entry entry1, + int hash2, + Entry entry2, + int hash3, + Entry entry3) { this.hash0 = hash0; this.entry0 = entry0; - + this.hash1 = hash1; this.entry1 = entry1; - + this.hash2 = hash2; this.entry2 = entry2; - + this.hash3 = hash3; this.entry3 = entry3; - + // this.hashFilter = hash0 | hash1 | hash2 | hash3; } - + Entry _entryAt(int index) { - switch ( index ) { + switch (index) { case 0: - return this.entry0; - + return this.entry0; + case 1: - return this.entry1; - + return this.entry1; + case 2: - return this.entry2; - + return this.entry2; + case 3: - return this.entry3; + return this.entry3; } - + return null; } - + int _hashAt(int index) { - switch ( index ) { + switch (index) { case 0: - return this.hash0; - + return this.hash0; + case 1: - return this.hash1; - + return this.hash1; + case 2: - return this.hash2; - + return this.hash2; + case 3: - return this.hash3; + return this.hash3; } - + return 0; } - + int sizeInChain() { int size = 0; - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { size += curGroup._size(); } return size; } - + int _size() { - return ( this.hash0 == 0 ? 0 : 1 ) + - ( this.hash1 == 0 ? 0 : 1 ) + - ( this.hash2 == 0 ? 0 : 1 ) + - ( this.hash3 == 0 ? 0 : 1 ); + return (this.hash0 == 0 ? 0 : 1) + + (this.hash1 == 0 ? 0 : 1) + + (this.hash2 == 0 ? 0 : 1) + + (this.hash3 == 0 ? 0 : 1); } - + boolean isEmptyChain() { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { - if ( !curGroup._isEmpty() ) return false; + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { + if (!curGroup._isEmpty()) return false; } return true; } - + boolean _isEmpty() { return (this.hash0 | this.hash1 | this.hash2 | this.hash3) == 0; // return (this.hashFilter == 0); } - + BucketGroup findContainingGroupInChain(int hash, String tag) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { - if ( curGroup._find(hash, tag) != null ) return curGroup; + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { + if (curGroup._find(hash, tag) != null) return curGroup; } return null; } - -// boolean _mayContain(int hash) { -// return ((hash & this.hashFilter) == hash); -// } - + + // boolean _mayContain(int hash) { + // return ((hash & this.hashFilter) == hash); + // } + Entry findInChain(int hash, String tag) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { Entry curEntry = curGroup._find(hash, tag); - if ( curEntry != null ) return curEntry; + if (curEntry != null) return curEntry; } return null; } Entry _find(int hash, String tag) { // if ( this._mayContain(hash) ) return null; - - if ( this.hash0 == hash && this.entry0.matches(tag)) { + + if (this.hash0 == hash && this.entry0.matches(tag)) { return this.entry0; - } else if ( this.hash1 == hash && this.entry1.matches(tag)) { + } else if (this.hash1 == hash && this.entry1.matches(tag)) { return this.entry1; - } else if ( this.hash2 == hash && this.entry2.matches(tag)) { + } else if (this.hash2 == hash && this.entry2.matches(tag)) { return this.entry2; - } else if ( this.hash3 == hash && this.entry3.matches(tag)) { + } else if (this.hash3 == hash && this.entry3.matches(tag)) { return this.entry3; } return null; } - + BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) { BucketGroup thisOrigHeadGroup = this; BucketGroup thisNewestHeadGroup = thisOrigHeadGroup; - - for ( BucketGroup thatCurGroup = thatHeadGroup; - thatCurGroup != null; - thatCurGroup = thatCurGroup.prev ) - { + + for (BucketGroup thatCurGroup = thatHeadGroup; + thatCurGroup != null; + thatCurGroup = thatCurGroup.prev) { // First phase - tries to replace or insert each entry in the existing bucket chain // Only need to search the original groups for replacements // The whole chain is eligible for insertions - boolean handled0 = (thatCurGroup.hash0 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash0, thatCurGroup.entry0) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash0, thatCurGroup.entry0); - - boolean handled1 = (thatCurGroup.hash1 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash1, thatCurGroup.entry1) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash1, thatCurGroup.entry1); - - boolean handled2 = (thatCurGroup.hash2 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash2, thatCurGroup.entry2) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash2, thatCurGroup.entry2); - - boolean handled3 = (thatCurGroup.hash3 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash3, thatCurGroup.entry3) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash3, thatCurGroup.entry3); - - // Second phase - takes any entries that weren't handled by phase 1 and puts them - // into a new BucketGroup. Since BucketGroups are fixed size, we know that the + boolean handled0 = + (thatCurGroup.hash0 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash0, thatCurGroup.entry0) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash0, thatCurGroup.entry0); + + boolean handled1 = + (thatCurGroup.hash1 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash1, thatCurGroup.entry1) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash1, thatCurGroup.entry1); + + boolean handled2 = + (thatCurGroup.hash2 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash2, thatCurGroup.entry2) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash2, thatCurGroup.entry2); + + boolean handled3 = + (thatCurGroup.hash3 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash3, thatCurGroup.entry3) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash3, thatCurGroup.entry3); + + // Second phase - takes any entries that weren't handled by phase 1 and puts them + // into a new BucketGroup. Since BucketGroups are fixed size, we know that the // left over entries from one BucketGroup will fit in the new BucketGroup. - if ( !handled0 || !handled1 || !handled2 || !handled3 ) { + if (!handled0 || !handled1 || !handled2 || !handled3) { // Rather than calling insert one time per entry // Exploiting the fact that the new group is known to be empty // And that BucketGroups are allowed to have holes in them (to allow for removal), - // so each unhandled entry from the source group is simply placed in + // so each unhandled entry from the source group is simply placed in // the same slot in the new group BucketGroup thisNewHashGroup = new BucketGroup(); int hashFilter = 0; - if ( !handled0 ) { + if (!handled0) { thisNewHashGroup.hash0 = thatCurGroup.hash0; thisNewHashGroup.entry0 = thatCurGroup.entry0; hashFilter |= thatCurGroup.hash0; } - if ( !handled1 ) { + if (!handled1) { thisNewHashGroup.hash1 = thatCurGroup.hash1; thisNewHashGroup.entry1 = thatCurGroup.entry1; hashFilter |= thatCurGroup.hash1; } - if ( !handled2 ) { + if (!handled2) { thisNewHashGroup.hash2 = thatCurGroup.hash2; thisNewHashGroup.entry2 = thatCurGroup.entry2; hashFilter |= thatCurGroup.hash2; } - if ( !handled3 ) { + if (!handled3) { thisNewHashGroup.hash3 = thatCurGroup.hash3; thisNewHashGroup.entry3 = thatCurGroup.entry3; hashFilter |= thatCurGroup.hash3; } // thisNewHashGroup.hashFilter = hashFilter; thisNewHashGroup.prev = thisNewestHeadGroup; - + thisNewestHeadGroup = thisNewHashGroup; } } - + return thisNewestHeadGroup; } - + boolean replaceOrInsertInChain(int hash, Entry entry) { - return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry); + return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry); } - + Entry replaceInChain(int hash, Entry entry) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { Entry prevEntry = curGroup._replace(hash, entry); - if ( prevEntry != null ) return prevEntry; + if (prevEntry != null) return prevEntry; } return null; } - + Entry _replace(int hash, Entry entry) { // if ( this._mayContain(hash) ) return null; - + // first check to see if the item is already present Entry prevEntry = null; - if ( this.hash0 == hash && this.entry0.matches(entry.tag) ) { + if (this.hash0 == hash && this.entry0.matches(entry.tag)) { prevEntry = this.entry0; this.entry0 = entry; - } else if ( this.hash1 == hash && this.entry1.matches(entry.tag) ) { + } else if (this.hash1 == hash && this.entry1.matches(entry.tag)) { prevEntry = this.entry1; this.entry1 = entry; - } else if ( this.hash2 == hash && this.entry2.matches(entry.tag) ) { + } else if (this.hash2 == hash && this.entry2.matches(entry.tag)) { prevEntry = this.entry2; this.entry2 = entry; - } else if ( this.hash3 == hash && this.entry3.matches(entry.tag) ) { + } else if (this.hash3 == hash && this.entry3.matches(entry.tag)) { prevEntry = this.entry3; this.entry3 = entry; } - + // no need to update this.hashFilter, since the hash is already included return prevEntry; } - + boolean insertInChain(int hash, Entry entry) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { - if ( curGroup._insert(hash, entry) ) return true; + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { + if (curGroup._insert(hash, entry)) return true; } return false; } - + boolean _insert(int hash, Entry entry) { boolean inserted = false; - if ( this.hash0 == 0 ) { + if (this.hash0 == 0) { this.hash0 = hash; this.entry0 = entry; - + // this.hashFilter |= hash; inserted = true; - } else if ( this.hash1 == 0 ) { + } else if (this.hash1 == 0) { this.hash1 = hash; this.entry1 = entry; - + // this.hashFilter |= hash; inserted = true; - } else if ( this.hash2 == 0 ) { + } else if (this.hash2 == 0) { this.hash2 = hash; this.entry2 = entry; - + // this.hashFilter |= hash; inserted = true; - } else if ( this.hash3 == 0 ) { + } else if (this.hash3 == 0) { this.hash3 = hash; this.entry3 = entry; - + // this.hashFilter |= hash; inserted = true; } return inserted; } - + BucketGroup removeGroupInChain(BucketGroup removeGroup) { BucketGroup firstGroup = this; - if ( firstGroup == removeGroup ) { + if (firstGroup == removeGroup) { return firstGroup.prev; } - - for ( BucketGroup priorGroup = firstGroup, curGroup = priorGroup.prev; - curGroup != null; - priorGroup = curGroup, curGroup = priorGroup.prev ) { - if ( curGroup == removeGroup ) { + + for (BucketGroup priorGroup = firstGroup, curGroup = priorGroup.prev; + curGroup != null; + priorGroup = curGroup, curGroup = priorGroup.prev) { + if (curGroup == removeGroup) { priorGroup.prev = curGroup.prev; } } @@ -1788,240 +1787,237 @@ BucketGroup removeGroupInChain(BucketGroup removeGroup) { Entry _remove(int hash, String tag) { Entry existingEntry = null; - if ( this.hash0 == hash && this.entry0.matches(tag)) { + if (this.hash0 == hash && this.entry0.matches(tag)) { existingEntry = this.entry0; - + this.hash0 = 0; this.entry0 = null; - } else if ( this.hash1 == hash && this.entry1.matches(tag) ) { + } else if (this.hash1 == hash && this.entry1.matches(tag)) { existingEntry = this.entry1; - + this.hash1 = 0; this.entry1 = null; - } else if ( this.hash2 == hash && this.entry2.matches(tag) ) { + } else if (this.hash2 == hash && this.entry2.matches(tag)) { existingEntry = this.entry2; - + this.hash2 = 0; this.entry2 = null; - } else if ( this.hash3 == hash && this.entry3.matches(tag) ) { + } else if (this.hash3 == hash && this.entry3.matches(tag)) { existingEntry = this.entry3; - + this.hash3 = 0; this.entry3 = null; } return existingEntry; } - + void forEachInChain(Consumer consumer) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(consumer); } } - + void _forEach(Consumer consumer) { - if ( this.entry0 != null ) consumer.accept(this.entry0); - if ( this.entry1 != null ) consumer.accept(this.entry1); - if ( this.entry2 != null ) consumer.accept(this.entry2); - if ( this.entry3 != null ) consumer.accept(this.entry3); + if (this.entry0 != null) consumer.accept(this.entry0); + if (this.entry1 != null) consumer.accept(this.entry1); + if (this.entry2 != null) consumer.accept(this.entry2); + if (this.entry3 != null) consumer.accept(this.entry3); } - + void forEachInChain(T thisObj, BiConsumer consumer) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(thisObj, consumer); } } - + void _forEach(T thisObj, BiConsumer consumer) { - if ( this.entry0 != null ) consumer.accept(thisObj, this.entry0); - if ( this.entry1 != null ) consumer.accept(thisObj, this.entry1); - if ( this.entry2 != null ) consumer.accept(thisObj, this.entry2); - if ( this.entry3 != null ) consumer.accept(thisObj, this.entry3); + if (this.entry0 != null) consumer.accept(thisObj, this.entry0); + if (this.entry1 != null) consumer.accept(thisObj, this.entry1); + if (this.entry2 != null) consumer.accept(thisObj, this.entry2); + if (this.entry3 != null) consumer.accept(thisObj, this.entry3); } - + void forEachInChain(T thisObj, U otherObj, TriConsumer consumer) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(thisObj, otherObj, consumer); } } void _forEach(T thisObj, U otherObj, TriConsumer consumer) { - if ( this.entry0 != null ) consumer.accept(thisObj, otherObj, this.entry0); - if ( this.entry1 != null ) consumer.accept(thisObj, otherObj, this.entry1); - if ( this.entry2 != null ) consumer.accept(thisObj, otherObj, this.entry2); - if ( this.entry3 != null ) consumer.accept(thisObj, otherObj, this.entry3); + if (this.entry0 != null) consumer.accept(thisObj, otherObj, this.entry0); + if (this.entry1 != null) consumer.accept(thisObj, otherObj, this.entry1); + if (this.entry2 != null) consumer.accept(thisObj, otherObj, this.entry2); + if (this.entry3 != null) consumer.accept(thisObj, otherObj, this.entry3); } - + void fillMapFromChain(Map map) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._fillMap(map); } } - + void _fillMap(Map map) { Entry entry0 = this.entry0; - if ( entry0 != null ) map.put(entry0.tag, entry0.objectValue()); + if (entry0 != null) map.put(entry0.tag, entry0.objectValue()); Entry entry1 = this.entry1; - if ( entry1 != null ) map.put(entry1.tag, entry1.objectValue()); - + if (entry1 != null) map.put(entry1.tag, entry1.objectValue()); + Entry entry2 = this.entry2; - if ( entry2 != null ) map.put(entry2.tag, entry2.objectValue()); - + if (entry2 != null) map.put(entry2.tag, entry2.objectValue()); + Entry entry3 = this.entry3; - if ( entry3 != null ) map.put(entry3.tag, entry3.objectValue()); + if (entry3 != null) map.put(entry3.tag, entry3.objectValue()); } - + void fillStringMapFromChain(Map map) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._fillStringMap(map); } } - + void _fillStringMap(Map map) { Entry entry0 = this.entry0; - if ( entry0 != null ) map.put(entry0.tag, entry0.stringValue()); + if (entry0 != null) map.put(entry0.tag, entry0.stringValue()); Entry entry1 = this.entry1; - if ( entry1 != null ) map.put(entry1.tag, entry1.stringValue()); - + if (entry1 != null) map.put(entry1.tag, entry1.stringValue()); + Entry entry2 = this.entry2; - if ( entry2 != null ) map.put(entry2.tag, entry2.stringValue()); - + if (entry2 != null) map.put(entry2.tag, entry2.stringValue()); + Entry entry3 = this.entry3; - if ( entry3 != null ) map.put(entry3.tag, entry3.stringValue()); + if (entry3 != null) map.put(entry3.tag, entry3.stringValue()); } - + BucketGroup cloneChain() { BucketGroup thisClone = this._cloneEntries(); - + BucketGroup thisPriorClone = thisClone; - for ( BucketGroup curGroup = this.prev; - curGroup != null; - curGroup = curGroup.prev ) - { + for (BucketGroup curGroup = this.prev; curGroup != null; curGroup = curGroup.prev) { BucketGroup newClone = curGroup._cloneEntries(); thisPriorClone.prev = newClone; - + thisPriorClone = newClone; } - + return thisClone; } - + BucketGroup _cloneEntries() { return new BucketGroup( - this.hash0, this.entry0, - this.hash1, this.entry1, - this.hash2, this.entry2, - this.hash3, this.entry3); + this.hash0, this.entry0, + this.hash1, this.entry1, + this.hash2, this.entry2, + this.hash3, this.entry3); } - + @Override public String toString() { StringBuilder builder = new StringBuilder(32); builder.append('['); - for ( int i = 0; i < BucketGroup.LEN; ++i ) { - if ( builder.length() != 0 ) builder.append(", "); - + for (int i = 0; i < BucketGroup.LEN; ++i) { + if (builder.length() != 0) builder.append(", "); + builder.append(this._entryAt(i)); } builder.append(']'); return builder.toString(); } } - + private static final class Entries extends AbstractSet> { private final TagMap map; - + Entries(TagMap map) { this.map = map; } - + @Override public int size() { return this.map.computeSize(); } - + @Override public boolean isEmpty() { return this.map.checkIfEmpty(); } - + @Override public Iterator> iterator() { @SuppressWarnings({"rawtypes", "unchecked"}) - Iterator> iter = (Iterator)this.map.iterator(); + Iterator> iter = (Iterator) this.map.iterator(); return iter; } } - + private static final class Keys extends AbstractSet { private final TagMap map; - + Keys(TagMap map) { this.map = map; } - + @Override public int size() { return this.map.computeSize(); } - + @Override public boolean isEmpty() { return this.map.checkIfEmpty(); } - + @Override public boolean contains(Object o) { return this.map.containsKey(o); } - + @Override public Iterator iterator() { return new KeysIterator(this.map); } } - + static final class KeysIterator extends MapIterator { KeysIterator(TagMap map) { super(map); } - + @Override public String next() { return this.nextEntry().tag(); } } - + private static final class Values extends AbstractCollection { private final TagMap map; - + Values(TagMap map) { this.map = map; } - + @Override public int size() { return this.map.computeSize(); } - + @Override public boolean isEmpty() { return this.map.checkIfEmpty(); } - + @Override public boolean contains(Object o) { return this.map.containsValue(o); } - + @Override public Iterator iterator() { return new ValuesIterator(this.map); } } - + static final class ValuesIterator extends MapIterator { ValuesIterator(TagMap map) { super(map); diff --git a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java index 296b93d0914..a08be92d248 100644 --- a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java +++ b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java @@ -1,9 +1,8 @@ package datadog.trace.api.gateway; -import datadog.trace.api.TagMap; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import java.util.Map; public interface IGSpanInfo { DDTraceId getTraceId(); 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 ef102de8e5f..31b610887ee 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,7 +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; 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 282261772e6..3e76d657069 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 @@ -2,8 +2,6 @@ import datadog.trace.api.TagMap; import datadog.trace.api.naming.NamingSchema; -import java.util.Collections; -import java.util.Map; import javax.annotation.Nonnull; public class PeerServiceNamingV0 implements NamingSchema.ForPeerService { @@ -14,6 +12,5 @@ public boolean supports() { @Nonnull @Override - public void tags(@Nonnull final TagMap unsafeTags) { - } + public void tags(@Nonnull final TagMap 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 4c64dda3a73..827a7489e09 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 @@ -71,8 +71,7 @@ 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 TagMap unsafeTags, @Nullable final String[] precursors) { if (precursors == null) { return false; } diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java index ea62a78008d..26e97b77e45 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java @@ -2,18 +2,16 @@ import static datadog.trace.bootstrap.instrumentation.api.InternalContextKeys.SPAN_KEY; -import datadog.trace.api.TagMap; import datadog.context.Context; import datadog.context.ContextKey; import datadog.context.ImplicitContextKeyed; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.IGSpanInfo; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.interceptor.MutableSpan; - -import java.util.Collections; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -96,7 +94,7 @@ default boolean isValid() { @Override TagMap getTags(); - + Object getTag(String key); @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java index 7754a1f92de..ebbc2e028f0 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java @@ -5,7 +5,6 @@ import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; -import java.util.Collections; import java.util.Map; /** diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java index 7cb834ca520..9e5f6c33a1f 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java @@ -7,7 +7,6 @@ import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.sampling.PrioritySampling; -import java.util.Map; class NoopSpan extends ImmutableSpan implements AgentSpan { static final NoopSpan INSTANCE = new NoopSpan(); @@ -78,10 +77,10 @@ public Integer getSamplingPriority() { public String getSpanType() { return null; } - + @Override public TagMap getTags() { - return TagMap.EMPTY; + return TagMap.EMPTY; } @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 0636a0932fe..f0b65e7b0c4 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -3,9 +3,9 @@ import static datadog.trace.api.TracePropagationStyle.NONE; import static java.util.Collections.emptyList; -import datadog.trace.api.TagMap; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.datastreams.PathwayContext; @@ -14,7 +14,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.TreeMap; /** * When calling extract, we allow for grabbing other configured headers as tags. Those tags are @@ -55,9 +54,9 @@ public TagContext( final TraceConfig traceConfig, final TracePropagationStyle propagationStyle, final DDTraceId traceId) { - - //if ( tags != null ) tags.checkWriteAccess(); - + + // if ( tags != null ) tags.checkWriteAccess(); + this.origin = origin; this.tags = tags; this.terminatedContextLinks = null; @@ -169,15 +168,15 @@ public String getCustomIpHeader() { } public final TagMap getTags() { - // DQH - Because of the lazy in putTag, this method effectively returns an immutable map - return ( this.tags == null ) ? TagMap.EMPTY: this.tags; + // DQH - Because of the lazy in putTag, this method effectively returns an immutable map + return (this.tags == null) ? TagMap.EMPTY : this.tags; } public void putTag(final String key, final String value) { - if ( this.tags == null ) { - this.tags = new TagMap(); - } - this.tags.set(key, value); + if (this.tags == null) { + this.tags = new TagMap(); + } + this.tags.set(key, value); } @Override diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index 987a2507551..16ad7b68226 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -8,84 +8,84 @@ public class TagMapBuilderTest { static final int SIZE = 32; - + @Test public void buildMutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + // just proving that the map is mutable map.set(key(1000), value(1000)); } - + @Test public void buildImmutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.buildImmutable(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + assertFrozen(map); } - + @Test public void buildWithRemoves() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - - for ( int i = 0; i < SIZE; i += 2 ) { + + for (int i = 0; i < SIZE; i += 2) { builder.remove(key(i)); } TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { - if ( (i % 2) == 0 ) { + for (int i = 0; i < SIZE; ++i) { + if ((i % 2) == 0) { assertNull(map.getString(key(i))); } else { assertEquals(value(i), map.getString(key(i))); } } } - + @Test public void reset() { TagMap.Builder builder = TagMap.builder(2); - + builder.put(key(0), value(0)); TagMap map0 = builder.build(); - + builder.reset(); - + builder.put(key(1), value(1)); TagMap map1 = builder.build(); - + assertEquals(value(0), map0.getString(key(0))); assertNull(map1.getString(key(0))); - + assertNull(map0.getString(key(1))); assertEquals(value(1), map1.getString(key(1))); } - + static final String key(int i) { return "key-" + i; } @@ -93,12 +93,12 @@ static final String key(int i) { static final String value(int i) { return "value-" + i; } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index cfb1b9c4929..0d66911d168 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -12,247 +12,247 @@ public void objectEntry() { TagMap.Entry entry = TagMap.Entry.newObjectEntry("foo", "bar"); assertKey("foo", entry); assertValue("bar", entry); - + assertEquals("bar", entry.stringValue()); - + assertTrue(entry.isObject()); } - + @Test public void anyEntry_object() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", "bar"); - + assertKey("foo", entry); assertValue("bar", entry); - + assertTrue(entry.isObject()); - + assertKey("foo", entry); assertValue("bar", entry); } - + @Test public void booleanEntry() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", true); - + assertKey("foo", entry); assertValue(true, entry); - + assertFalse(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.BOOLEAN)); } - + @Test public void booleanEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); - + assertKey("foo", entry); assertValue(true, entry); - + assertFalse(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.BOOLEAN)); } - + @Test public void anyEntry_boolean() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); assertKey("foo", entry); assertValue(true, entry); - + assertFalse(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.BOOLEAN)); - + assertValue(true, entry); } - + @Test public void intEntry() { TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", 20); - + assertKey("foo", entry); assertValue(20, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.INT)); } - + @Test public void intEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)); - + assertKey("foo", entry); assertValue(20, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.INT)); } - + @Test public void anyEntry_int() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)); assertKey("foo", entry); assertValue(20, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.INT)); assertValue(20, entry); } - + @Test public void longEntry() { TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", 1_048_576L); - + assertKey("foo", entry); assertValue(1_048_576L, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.LONG)); } - + @Test public void longEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)); - + assertKey("foo", entry); assertValue(1_048_576L, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.LONG)); } - + @Test public void anyEntry_long() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)); - + assertKey("foo", entry); assertValue(1_048_576L, entry); - + // type checks force any resolution assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.LONG)); - + // check value again after resolution assertValue(1_048_576L, entry); } - + @Test public void doubleEntry() { - TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); - + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); + assertKey("foo", entry); assertValue(Math.PI, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.DOUBLE)); } - + @Test public void doubleEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); - + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); + assertKey("foo", entry); assertValue(Math.PI, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.DOUBLE)); } - + @Test public void anyEntry_double() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)); - + assertKey("foo", entry); assertValue(Math.PI, entry); - + // type checks force any resolution assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.DOUBLE)); - + // check value again after resolution assertValue(Math.PI, entry); } - + @Test public void floatEntry() { - TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); - + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); + assertKey("foo", entry); assertValue(2.718281828f, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.FLOAT)); } - + @Test public void floatEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); - + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); + assertKey("foo", entry); assertValue(2.718281828f, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.FLOAT)); } - + static final void assertKey(String expected, TagMap.Entry entry) { assertEquals(expected, entry.tag()); assertEquals(expected, entry.getKey()); } - + static final void assertValue(Object expected, TagMap.Entry entry) { assertEquals(expected, entry.objectValue()); assertEquals(expected, entry.getValue()); - + assertEquals(expected.toString(), entry.stringValue()); } - + static final void assertValue(boolean expected, TagMap.Entry entry) { assertEquals(expected, entry.booleanValue()); assertEquals(Boolean.valueOf(expected), entry.objectValue()); - + assertEquals(Boolean.toString(expected), entry.stringValue()); } - + static final void assertValue(int expected, TagMap.Entry entry) { assertEquals(expected, entry.intValue()); - assertEquals((long)expected, entry.longValue()); - assertEquals((float)expected, entry.floatValue()); - assertEquals((double)expected, entry.doubleValue()); + assertEquals((long) expected, entry.longValue()); + assertEquals((float) expected, entry.floatValue()); + assertEquals((double) expected, entry.doubleValue()); assertEquals(Integer.valueOf(expected), entry.objectValue()); - + assertEquals(Integer.toString(expected), entry.stringValue()); } - + static final void assertValue(long expected, TagMap.Entry entry) { assertEquals(expected, entry.longValue()); - assertEquals((int)expected, entry.intValue()); - assertEquals((float)expected, entry.floatValue()); - assertEquals((double)expected, entry.doubleValue()); + assertEquals((int) expected, entry.intValue()); + assertEquals((float) expected, entry.floatValue()); + assertEquals((double) expected, entry.doubleValue()); assertEquals(Long.valueOf(expected), entry.objectValue()); - + assertEquals(Long.toString(expected), entry.stringValue()); } static final void assertValue(double expected, TagMap.Entry entry) { assertEquals(expected, entry.doubleValue()); - assertEquals((int)expected, entry.intValue()); - assertEquals((long)expected, entry.longValue()); - assertEquals((float)expected, entry.floatValue()); + assertEquals((int) expected, entry.intValue()); + assertEquals((long) expected, entry.longValue()); + assertEquals((float) expected, entry.floatValue()); assertEquals(Double.valueOf(expected), entry.objectValue()); - + assertEquals(Double.toString(expected), entry.stringValue()); } - + static final void assertValue(float expected, TagMap.Entry entry) { assertEquals(expected, entry.floatValue()); - assertEquals((int)expected, entry.intValue()); - assertEquals((long)expected, entry.longValue()); - assertEquals((double)expected, entry.doubleValue()); + assertEquals((int) expected, entry.intValue()); + assertEquals((long) expected, entry.longValue()); + assertEquals((double) expected, entry.doubleValue()); assertEquals(Float.valueOf(expected), entry.objectValue()); - + assertEquals(Float.toString(expected), entry.stringValue()); } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index 053997f7bd4..91345c5c2ff 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -11,431 +11,1204 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; - import org.junit.jupiter.api.Test; public final class TagMapFuzzTest { static final int NUM_KEYS = 128; static final int MAX_NUM_ACTIONS = 32; - + @Test void test() { test(generateTest()); } - + @Test void testMerge() { TestCase mapACase = generateTest(); TestCase mapBCase = generateTest(); - + TagMap tagMapA = test(mapACase); TagMap tagMapB = test(mapBCase); - + HashMap hashMapA = new HashMap<>(tagMapA); HashMap hashMapB = new HashMap<>(tagMapB); - + tagMapA.putAll(tagMapB); hashMapA.putAll(hashMapB); - + assertMapEquals(hashMapA, tagMapA); } - + @Test void priorFailingCase0() { - TagMap map = makeTagMap( - remove("key-4"), - put("key-71","values-443049055"), - put("key-2","values-1227065898"), - put("key-25","values-696891692"), - put("key-93","values-763707175"), - put("key-23","values--1514091210"), - put("key-16","values--1388742686") - ); - - MapAction failingAction = putAllTagMap("key-17","values--2085338893","key-51","values-960243765","key-33","values-1493544499","key-46","values-697926849","key-70","values--184054454","key-67","values-374577326","key-9","values--742453833","key-11","values-1606950841","key-119","values--1914593057","key-53","values-375236438","key-96","values--107185569","key-47","values--1276407408","key-125","values--1627172151","key-110","values--1227150283","key-15","values-380379920","key-42","values--632271048","key-99","values--650090786","key-8","values--1990889145","key-103","values-1815698254","key-120","values-279025031","key-93","values-589795963","key-12","values--935895941","key-105","values-94976227","key-85","values--424609970","key-78","values-1231948102","key-115","values-88670282","key-26","values-733903384","key-100","values-2102967487","key-74","values-958598087","key-104","values-264458254","key-125","values--1781797927","key-27","values--562810078","key-7","values--376776745","key-111","values-263564677","key-50","values--859673100","key-57","values-1585057281","key-48","values--617889787","key-98","values--1878108220","key-9","values--227223375","key-59","values-1577082288","key-94","values--268049040","key-0","values-1708355496","key-62","values--733451297","key-14","values-232732747","key-4","values--406605642","key-58","values-1772476833","key-8","values--1155025225","key-101","values-144480545","key-66","values-355117269","key-121","values-1858008722","key-33","values-1947754079","key-1","values--1475603838","key-125","values--2146772243","key-117","values-852022714","key-53","values--2039348506","key-65","values-2011228657","key-108","values-1581592518","key-17","values-2129571020","key-5","values-1106900841","key-80","values-1791757923","key-18","values--1992962227","key-2","values-328863878","key-110","values-1182949334","key-5","values-1049403346","key-107","values-1246502060","key-115","values-2053931423","key-19","values--1731179633","key-104","values--1090790550","key-67","values--1312759979","key-10","values-1411135","key-109","values--1784920248","key-20","values--827644780","key-55","values--1610270998","key-60","values-1287959520","key-31","values-1686541667","key-41","values-399844058","key-115","values-2045201464","key-78","values-358081227","key-57","values--1374149269","key-65","values-1871734555","key-124","values--211494558","key-119","values-1757597102","key-32","values--336988038","key-85","values-1415155858","key-44","values-1455425178","key-48","values--325658059","key-68","values--793590840","key-96","values--2010766492","key-40","values-2007171160","key-29","values-186945230","key-63","values-1741962849","key-26","values-948582805","key-31","values-47004766","key-90","values-1304302008","key-69","values-2120328211","key-111","values-2053321468","key-69","values--498524858","key-125","values--193004619","key-30","values--1142090845","key-15","values--1334900170","key-33","values-1011001500","key-55","values-452401605","key-18","values-1260118555","key-44","values--1109396459","key-2","values--555647718","key-61","values-1060742038","key-51","values--827099230","key-62","values--1443716296","key-16","values-534556355","key-81","values--787910427","key-20","values-1429697120","key-36","values--1775988293","key-66","values-624669635","key-25","values--684183265","key-26","values-293626449","key-91","values--1212867803","key-6","values-1778251481","key-83","values-1257370908","key-92","values--1120490028","key-111","values-9646496","key-90","values-1485206899"); + TagMap map = + makeTagMap( + remove("key-4"), + put("key-71", "values-443049055"), + put("key-2", "values-1227065898"), + put("key-25", "values-696891692"), + put("key-93", "values-763707175"), + put("key-23", "values--1514091210"), + put("key-16", "values--1388742686")); + + MapAction failingAction = + putAllTagMap( + "key-17", + "values--2085338893", + "key-51", + "values-960243765", + "key-33", + "values-1493544499", + "key-46", + "values-697926849", + "key-70", + "values--184054454", + "key-67", + "values-374577326", + "key-9", + "values--742453833", + "key-11", + "values-1606950841", + "key-119", + "values--1914593057", + "key-53", + "values-375236438", + "key-96", + "values--107185569", + "key-47", + "values--1276407408", + "key-125", + "values--1627172151", + "key-110", + "values--1227150283", + "key-15", + "values-380379920", + "key-42", + "values--632271048", + "key-99", + "values--650090786", + "key-8", + "values--1990889145", + "key-103", + "values-1815698254", + "key-120", + "values-279025031", + "key-93", + "values-589795963", + "key-12", + "values--935895941", + "key-105", + "values-94976227", + "key-85", + "values--424609970", + "key-78", + "values-1231948102", + "key-115", + "values-88670282", + "key-26", + "values-733903384", + "key-100", + "values-2102967487", + "key-74", + "values-958598087", + "key-104", + "values-264458254", + "key-125", + "values--1781797927", + "key-27", + "values--562810078", + "key-7", + "values--376776745", + "key-111", + "values-263564677", + "key-50", + "values--859673100", + "key-57", + "values-1585057281", + "key-48", + "values--617889787", + "key-98", + "values--1878108220", + "key-9", + "values--227223375", + "key-59", + "values-1577082288", + "key-94", + "values--268049040", + "key-0", + "values-1708355496", + "key-62", + "values--733451297", + "key-14", + "values-232732747", + "key-4", + "values--406605642", + "key-58", + "values-1772476833", + "key-8", + "values--1155025225", + "key-101", + "values-144480545", + "key-66", + "values-355117269", + "key-121", + "values-1858008722", + "key-33", + "values-1947754079", + "key-1", + "values--1475603838", + "key-125", + "values--2146772243", + "key-117", + "values-852022714", + "key-53", + "values--2039348506", + "key-65", + "values-2011228657", + "key-108", + "values-1581592518", + "key-17", + "values-2129571020", + "key-5", + "values-1106900841", + "key-80", + "values-1791757923", + "key-18", + "values--1992962227", + "key-2", + "values-328863878", + "key-110", + "values-1182949334", + "key-5", + "values-1049403346", + "key-107", + "values-1246502060", + "key-115", + "values-2053931423", + "key-19", + "values--1731179633", + "key-104", + "values--1090790550", + "key-67", + "values--1312759979", + "key-10", + "values-1411135", + "key-109", + "values--1784920248", + "key-20", + "values--827644780", + "key-55", + "values--1610270998", + "key-60", + "values-1287959520", + "key-31", + "values-1686541667", + "key-41", + "values-399844058", + "key-115", + "values-2045201464", + "key-78", + "values-358081227", + "key-57", + "values--1374149269", + "key-65", + "values-1871734555", + "key-124", + "values--211494558", + "key-119", + "values-1757597102", + "key-32", + "values--336988038", + "key-85", + "values-1415155858", + "key-44", + "values-1455425178", + "key-48", + "values--325658059", + "key-68", + "values--793590840", + "key-96", + "values--2010766492", + "key-40", + "values-2007171160", + "key-29", + "values-186945230", + "key-63", + "values-1741962849", + "key-26", + "values-948582805", + "key-31", + "values-47004766", + "key-90", + "values-1304302008", + "key-69", + "values-2120328211", + "key-111", + "values-2053321468", + "key-69", + "values--498524858", + "key-125", + "values--193004619", + "key-30", + "values--1142090845", + "key-15", + "values--1334900170", + "key-33", + "values-1011001500", + "key-55", + "values-452401605", + "key-18", + "values-1260118555", + "key-44", + "values--1109396459", + "key-2", + "values--555647718", + "key-61", + "values-1060742038", + "key-51", + "values--827099230", + "key-62", + "values--1443716296", + "key-16", + "values-534556355", + "key-81", + "values--787910427", + "key-20", + "values-1429697120", + "key-36", + "values--1775988293", + "key-66", + "values-624669635", + "key-25", + "values--684183265", + "key-26", + "values-293626449", + "key-91", + "values--1212867803", + "key-6", + "values-1778251481", + "key-83", + "values-1257370908", + "key-92", + "values--1120490028", + "key-111", + "values-9646496", + "key-90", + "values-1485206899"); failingAction.apply(map); failingAction.verify(map); } - + @Test void priorFailingCase1() { - TagMap map = makeTagMap( - put("key-68","values--37178328"), - put("key-93","values--2093086281") - ); - - MapAction failingAction = putAllTagMap("key-36","values--1951535044","key-59","values--1045985660","key-68","values-1270827526","key-65","values-440073158","key-91","values-954365843","key-75","values-1014366449","key-117","values--1306617705","key-90","values-984567966","key-120","values--1802603599","key-56","values-319574488","key-78","values--711288173","key-103","values-694279462","key-84","values-1391260657","key-59","values--484807195","key-67","values-1675498322","key-91","values--227731796","key-105","values--1471022333","key-112","values--755617374","key-117","values--668324524","key-65","values-1165174761","key-13","values--1947081814","key-72","values-2032502631","key-106","values-256372025","key-71","values--995163162","key-92","values-972782926","key-116","values-25012447","key-23","values--979671053","key-94","values-367125724","key-48","values--2011523144","key-14","values-578926680","key-65","values-1325737627","key-89","values-1539092266","key-100","values--319629978","key-53","values-1125496255","key-2","values-1988036327","key-105","values--1333468536","key-37","values-351345678","key-4","values-683252782","key-62","values--1466612877","key-100","values-268100559","key-104","values-3517495","key-48","values--1588410835","key-42","values--180653405","key-118","values--1181647255","key-17","values-509279769","key-33","values-298668287","key-76","values-2062435628","key-18","values-287811864","key-46","values--1337930894","key-50","values-2089310564","key-24","values--1870293199","key-47","values--1155431370","key-81","values--1507929564","key-115","values-1149614815","key-57","values--334611395","key-86","values-146447703","key-107","values-938082683","key-38","values-338654203","key-40","values--376260149","key-20","values--860844060","key-20","values-2003129702","key-75","values--1787311067","key-39","values--1988768973","key-58","values--479797619","key-16","values-571033631","key-65","values--1867296166","key-56","values--2071960469","key-12","values-821930484","key-40","values--54692885","key-65","values-328817493","key-121","values-1276016318","key-33","values--2081652233","key-31","values-381335133","key-77","values-1486312656","key-48","values--1058365372","key-109","values--733344537","key-85","values-1236864082","key-35","values-2045087594","key-49","values-1990762822","key-38","values--1582706513","key-18","values--626997990","key-80","values--1995264473","key-126","values--558193472","key-83","values-415016167","key-53","values-1348674948","key-58","values-612738550","key-12","values-417676134","key-101","values--58098778","key-127","values-1658306930","key-17","values-985378289","key-68","values-686600535","key-36","values-365513638","key-87","values--1737233661","key-67","values--1840935230","key-8","values-540289596","key-11","values--2045114386","key-38","values--786598887","key-48","values-1877144385","key-5","values-65838542","key-18","values-263200779","key-120","values--1500947489","key-65","values-769990109","key-38","values-1886840000","key-29","values--48760205","key-61","values--1942966789"); + TagMap map = makeTagMap(put("key-68", "values--37178328"), put("key-93", "values--2093086281")); + + MapAction failingAction = + putAllTagMap( + "key-36", + "values--1951535044", + "key-59", + "values--1045985660", + "key-68", + "values-1270827526", + "key-65", + "values-440073158", + "key-91", + "values-954365843", + "key-75", + "values-1014366449", + "key-117", + "values--1306617705", + "key-90", + "values-984567966", + "key-120", + "values--1802603599", + "key-56", + "values-319574488", + "key-78", + "values--711288173", + "key-103", + "values-694279462", + "key-84", + "values-1391260657", + "key-59", + "values--484807195", + "key-67", + "values-1675498322", + "key-91", + "values--227731796", + "key-105", + "values--1471022333", + "key-112", + "values--755617374", + "key-117", + "values--668324524", + "key-65", + "values-1165174761", + "key-13", + "values--1947081814", + "key-72", + "values-2032502631", + "key-106", + "values-256372025", + "key-71", + "values--995163162", + "key-92", + "values-972782926", + "key-116", + "values-25012447", + "key-23", + "values--979671053", + "key-94", + "values-367125724", + "key-48", + "values--2011523144", + "key-14", + "values-578926680", + "key-65", + "values-1325737627", + "key-89", + "values-1539092266", + "key-100", + "values--319629978", + "key-53", + "values-1125496255", + "key-2", + "values-1988036327", + "key-105", + "values--1333468536", + "key-37", + "values-351345678", + "key-4", + "values-683252782", + "key-62", + "values--1466612877", + "key-100", + "values-268100559", + "key-104", + "values-3517495", + "key-48", + "values--1588410835", + "key-42", + "values--180653405", + "key-118", + "values--1181647255", + "key-17", + "values-509279769", + "key-33", + "values-298668287", + "key-76", + "values-2062435628", + "key-18", + "values-287811864", + "key-46", + "values--1337930894", + "key-50", + "values-2089310564", + "key-24", + "values--1870293199", + "key-47", + "values--1155431370", + "key-81", + "values--1507929564", + "key-115", + "values-1149614815", + "key-57", + "values--334611395", + "key-86", + "values-146447703", + "key-107", + "values-938082683", + "key-38", + "values-338654203", + "key-40", + "values--376260149", + "key-20", + "values--860844060", + "key-20", + "values-2003129702", + "key-75", + "values--1787311067", + "key-39", + "values--1988768973", + "key-58", + "values--479797619", + "key-16", + "values-571033631", + "key-65", + "values--1867296166", + "key-56", + "values--2071960469", + "key-12", + "values-821930484", + "key-40", + "values--54692885", + "key-65", + "values-328817493", + "key-121", + "values-1276016318", + "key-33", + "values--2081652233", + "key-31", + "values-381335133", + "key-77", + "values-1486312656", + "key-48", + "values--1058365372", + "key-109", + "values--733344537", + "key-85", + "values-1236864082", + "key-35", + "values-2045087594", + "key-49", + "values-1990762822", + "key-38", + "values--1582706513", + "key-18", + "values--626997990", + "key-80", + "values--1995264473", + "key-126", + "values--558193472", + "key-83", + "values-415016167", + "key-53", + "values-1348674948", + "key-58", + "values-612738550", + "key-12", + "values-417676134", + "key-101", + "values--58098778", + "key-127", + "values-1658306930", + "key-17", + "values-985378289", + "key-68", + "values-686600535", + "key-36", + "values-365513638", + "key-87", + "values--1737233661", + "key-67", + "values--1840935230", + "key-8", + "values-540289596", + "key-11", + "values--2045114386", + "key-38", + "values--786598887", + "key-48", + "values-1877144385", + "key-5", + "values-65838542", + "key-18", + "values-263200779", + "key-120", + "values--1500947489", + "key-65", + "values-769990109", + "key-38", + "values-1886840000", + "key-29", + "values--48760205", + "key-61", + "values--1942966789"); failingAction.apply(map); failingAction.verify(map); } - + @Test void priorFailingCase2() { - TestCase testCase = new TestCase( - remove("key-34"), - put("key-122","values-1828753938"), - putAll("key-123","values--118789056","key-28","values--751841781","key-105","values-1663318183","key-63","values--2036414463","key-74","values-1584612783","key-118","values--414681411","key-67","values-1154668404","key-1","values--1755856616","key-89","values--344740102","key-110","values-1884649283","key-1","values--1420345075","key-22","values-1951712698","key-103","values-488559164","key-8","values-1180668912","key-44","values-290310046","key-105","values--303926067","key-26","values-910376351","key-59","values-1600204544","key-23","values-425861746","key-76","values--1045446587","key-21","values-453905226","key-1","values-286624672","key-69","values-934359656","key-57","values--1890465763","key-13","values--1949062639","key-68","values-242077328","key-42","values--1584075743","key-46","values--1306318288","key-31","values--848418043","key-71","values--1547961101","key-121","values--1493693636","key-24","values-330660358","key-24","values--1466871690","key-91","values--995064376","key-18","values-1615316779","key-124","values--296191510","key-52","values-740309054","key-8","values-1777392898","key-73","values-92831985","key-13","values--1711360891","key-114","values-1960346620","key-44","values--1599497099","key-107","values-668485357","key-116","values--1792788504"), - put("key-123","values--1844485682"), - putAll("key-64","values--1694520036","key-17","values--469732912","key-79","values--1293521097","key-11","values--2000592955","key-98","values-517073723","key-28","values-1085152681","key-34","values-1943586726","key-3","values-216087991","key-97","values-222660872","key-41","values-90906196","key-63","values--934208984","key-57","values-327167184","key-111","values--1059115125","key-75","values--2031064209","key-8","values-1924310140","key-69","values--362514182","key-90","values-852043703","key-98","values--998302860","key-49","values-1658920804","key-106","values--227162298","key-25","values-493046373","key-52","values--555623542","key-77","values--717275660","key-31","values-1930766287","key-69","values--1367213079","key-38","values--1112081116","key-65","values--1916889923","key-96","values-157036191","key-127","values--302553995","key-38","values-485874872","key-110","values--855874569","key-39","values--390829775","key-7","values--452123269","key-63","values--527204905","key-101","values-166173307","key-126","values-1050454498","key-4","values--215188400","key-25","values-947961204","key-42","values-145803888","key-1","values--970532578","key-43","values--1675493776","key-29","values-1193328809","key-108","values-1302659140","key-120","values--1722764270","key-24","values--483238806","key-53","values-611589672","key-39","values--229429656","key-29","values--733337788","key-9","values-736222322","key-74","values--950770749","key-91","values-202817768","key-95","values-500260096","key-71","values--1798188865","key-12","values--1936098297","key-28","values--2116134632","key-21","values-799594067","key-68","values--333178107","key-50","values-445767791","key-88","values-1307699662","key-69","values--110615017","key-25","values-699603233","key-101","values--2093413536","key-91","values--2022040839","key-45","values-888546703","key-40","values--2140684954","key-1","values-371033654","key-68","values--20293415","key-59","values-697437101","key-43","values--1145022834","key-62","values--2125187195","key-15","values--1062944166","key-103","values--889634836","key-125","values-8694763","key-101","values--281475498","key-13","values-1972488719","key-32","values-1900833863","key-119","values--926978044","key-82","values-288820151","key-78","values--303310027","key-25","values--1284661437","key-47","values-1624726045","key-14","values-1658036950","key-65","values-1629683219","key-10","values-275264679","key-126","values--592085694","key-32","values-1844385705","key-85","values--1815321660","key-72","values-918231225","key-91","values-675699466","key-121","values--2008685332","key-61","values--1398921570","key-19","values-617817427","key-122","values--793708860","key-41","values--2027225350","key-41","values-1194206680","key-1","values-1116090448","key-49","values-1662444555","key-54","values-747436284","key-118","values--1367237858","key-65","values-133495093","key-73","values--1451855551","key-43","values--357794833","key-76","values-129403123","key-59","values--65688873","key-22","values-480031738","key-73","values--310815862","key-0","values--1734944386","key-56","values--540459893","key-38","values-1308912555","key-2","values--2073028093","key-14","values--693713438","key-76","values-295450436","key-113","values--2065146687","key-0","values-2076623027","key-17","values--1394046356","key-78","values--2014478659","key-5","values--665180960"), - put("key-124","values-460160716"), - put("key-112","values--1828904046"), - put("key-41","values--904162962")); - + TestCase testCase = + new TestCase( + remove("key-34"), + put("key-122", "values-1828753938"), + putAll( + "key-123", + "values--118789056", + "key-28", + "values--751841781", + "key-105", + "values-1663318183", + "key-63", + "values--2036414463", + "key-74", + "values-1584612783", + "key-118", + "values--414681411", + "key-67", + "values-1154668404", + "key-1", + "values--1755856616", + "key-89", + "values--344740102", + "key-110", + "values-1884649283", + "key-1", + "values--1420345075", + "key-22", + "values-1951712698", + "key-103", + "values-488559164", + "key-8", + "values-1180668912", + "key-44", + "values-290310046", + "key-105", + "values--303926067", + "key-26", + "values-910376351", + "key-59", + "values-1600204544", + "key-23", + "values-425861746", + "key-76", + "values--1045446587", + "key-21", + "values-453905226", + "key-1", + "values-286624672", + "key-69", + "values-934359656", + "key-57", + "values--1890465763", + "key-13", + "values--1949062639", + "key-68", + "values-242077328", + "key-42", + "values--1584075743", + "key-46", + "values--1306318288", + "key-31", + "values--848418043", + "key-71", + "values--1547961101", + "key-121", + "values--1493693636", + "key-24", + "values-330660358", + "key-24", + "values--1466871690", + "key-91", + "values--995064376", + "key-18", + "values-1615316779", + "key-124", + "values--296191510", + "key-52", + "values-740309054", + "key-8", + "values-1777392898", + "key-73", + "values-92831985", + "key-13", + "values--1711360891", + "key-114", + "values-1960346620", + "key-44", + "values--1599497099", + "key-107", + "values-668485357", + "key-116", + "values--1792788504"), + put("key-123", "values--1844485682"), + putAll( + "key-64", + "values--1694520036", + "key-17", + "values--469732912", + "key-79", + "values--1293521097", + "key-11", + "values--2000592955", + "key-98", + "values-517073723", + "key-28", + "values-1085152681", + "key-34", + "values-1943586726", + "key-3", + "values-216087991", + "key-97", + "values-222660872", + "key-41", + "values-90906196", + "key-63", + "values--934208984", + "key-57", + "values-327167184", + "key-111", + "values--1059115125", + "key-75", + "values--2031064209", + "key-8", + "values-1924310140", + "key-69", + "values--362514182", + "key-90", + "values-852043703", + "key-98", + "values--998302860", + "key-49", + "values-1658920804", + "key-106", + "values--227162298", + "key-25", + "values-493046373", + "key-52", + "values--555623542", + "key-77", + "values--717275660", + "key-31", + "values-1930766287", + "key-69", + "values--1367213079", + "key-38", + "values--1112081116", + "key-65", + "values--1916889923", + "key-96", + "values-157036191", + "key-127", + "values--302553995", + "key-38", + "values-485874872", + "key-110", + "values--855874569", + "key-39", + "values--390829775", + "key-7", + "values--452123269", + "key-63", + "values--527204905", + "key-101", + "values-166173307", + "key-126", + "values-1050454498", + "key-4", + "values--215188400", + "key-25", + "values-947961204", + "key-42", + "values-145803888", + "key-1", + "values--970532578", + "key-43", + "values--1675493776", + "key-29", + "values-1193328809", + "key-108", + "values-1302659140", + "key-120", + "values--1722764270", + "key-24", + "values--483238806", + "key-53", + "values-611589672", + "key-39", + "values--229429656", + "key-29", + "values--733337788", + "key-9", + "values-736222322", + "key-74", + "values--950770749", + "key-91", + "values-202817768", + "key-95", + "values-500260096", + "key-71", + "values--1798188865", + "key-12", + "values--1936098297", + "key-28", + "values--2116134632", + "key-21", + "values-799594067", + "key-68", + "values--333178107", + "key-50", + "values-445767791", + "key-88", + "values-1307699662", + "key-69", + "values--110615017", + "key-25", + "values-699603233", + "key-101", + "values--2093413536", + "key-91", + "values--2022040839", + "key-45", + "values-888546703", + "key-40", + "values--2140684954", + "key-1", + "values-371033654", + "key-68", + "values--20293415", + "key-59", + "values-697437101", + "key-43", + "values--1145022834", + "key-62", + "values--2125187195", + "key-15", + "values--1062944166", + "key-103", + "values--889634836", + "key-125", + "values-8694763", + "key-101", + "values--281475498", + "key-13", + "values-1972488719", + "key-32", + "values-1900833863", + "key-119", + "values--926978044", + "key-82", + "values-288820151", + "key-78", + "values--303310027", + "key-25", + "values--1284661437", + "key-47", + "values-1624726045", + "key-14", + "values-1658036950", + "key-65", + "values-1629683219", + "key-10", + "values-275264679", + "key-126", + "values--592085694", + "key-32", + "values-1844385705", + "key-85", + "values--1815321660", + "key-72", + "values-918231225", + "key-91", + "values-675699466", + "key-121", + "values--2008685332", + "key-61", + "values--1398921570", + "key-19", + "values-617817427", + "key-122", + "values--793708860", + "key-41", + "values--2027225350", + "key-41", + "values-1194206680", + "key-1", + "values-1116090448", + "key-49", + "values-1662444555", + "key-54", + "values-747436284", + "key-118", + "values--1367237858", + "key-65", + "values-133495093", + "key-73", + "values--1451855551", + "key-43", + "values--357794833", + "key-76", + "values-129403123", + "key-59", + "values--65688873", + "key-22", + "values-480031738", + "key-73", + "values--310815862", + "key-0", + "values--1734944386", + "key-56", + "values--540459893", + "key-38", + "values-1308912555", + "key-2", + "values--2073028093", + "key-14", + "values--693713438", + "key-76", + "values-295450436", + "key-113", + "values--2065146687", + "key-0", + "values-2076623027", + "key-17", + "values--1394046356", + "key-78", + "values--2014478659", + "key-5", + "values--665180960"), + put("key-124", "values-460160716"), + put("key-112", "values--1828904046"), + put("key-41", "values--904162962")); + Map expected = makeMap(testCase); TagMap actual = makeTagMap(testCase); - + MapAction failingAction = remove("key-127"); failingAction.apply(expected); failingAction.verify(expected); - + failingAction.apply(actual); failingAction.verify(actual); - + assertMapEquals(expected, actual); } - + public static final TagMap test(MapAction... actions) { return test(new TestCase(Arrays.asList(actions))); } - + public static final Map makeMap(TestCase testCase) { return makeMap(testCase.actions); } - + public static final Map makeMap(MapAction... actions) { return makeMap(Arrays.asList(actions)); } - + public static final Map makeMap(List actions) { Map map = new HashMap<>(); - for ( MapAction action: actions ) { + for (MapAction action : actions) { action.apply(map); } return map; } - + public static final TagMap makeTagMap(TestCase testCase) { return makeTagMap(testCase.actions); } - + public static final TagMap makeTagMap(MapAction... actions) { return makeTagMap(Arrays.asList(actions)); } - + public static final TagMap makeTagMap(List actions) { TagMap map = new TagMap(); - for ( MapAction action: actions ) { + for (MapAction action : actions) { action.apply(map); } return map; } - + public static final TagMap test(TestCase test) { List actions = test.actions(); - + Map hashMap = new HashMap<>(); TagMap tagMap = new TagMap(); int actionIndex = 0; try { - for ( actionIndex = 0; actionIndex < actions.size(); ++actionIndex ) { + for (actionIndex = 0; actionIndex < actions.size(); ++actionIndex) { MapAction action = actions.get(actionIndex); - + Object expected = action.apply(hashMap); Object result = action.apply(tagMap); - + assertEquals(expected, result); - + action.verify(tagMap); - + assertMapEquals(hashMap, tagMap); } - } catch ( Error e ) { + } catch (Error e) { System.err.println(new TestCase(actions.subList(0, actionIndex + 1))); - + throw e; - } + } return tagMap; } - + public static final TestCase generateTest() { return generateTest(ThreadLocalRandom.current().nextInt(MAX_NUM_ACTIONS)); } - + public static final TestCase generateTest(int size) { List actions = new ArrayList<>(size); - for ( int i = 0; i < size; ++i ) { + for (int i = 0; i < size; ++i) { actions.add(randomAction()); } return new TestCase(actions); } - + public static final MapAction randomAction() { float actionSelector = ThreadLocalRandom.current().nextFloat(); - - if ( actionSelector > 0.5 ) { + + if (actionSelector > 0.5) { // 50% puts return put(randomKey(), randomValue()); - } else if ( actionSelector > 0.3 ) { + } else if (actionSelector > 0.3) { // 20% removes return remove(randomKey()); - } else if ( actionSelector > 0.2 ) { + } else if (actionSelector > 0.2) { // 10% putAll TagMap return putAllTagMap(randomKeysAndValues()); - } else if ( actionSelector > 0.02 ) { + } else if (actionSelector > 0.02) { // ~10% putAll HashMap return putAll(randomKeysAndValues()); } else { return clear(); } } - + public static final MapAction put(String key, String value) { return new Put(key, value); } - + public static final MapAction putAll(String... keysAndValues) { return new PutAll(keysAndValues); } - + public static final MapAction putAllTagMap(String... keysAndValues) { return new PutAllTagMap(keysAndValues); } - + public static final MapAction clear() { return Clear.INSTANCE; } - + public static final MapAction remove(String key) { return new Remove(key); } - + static final void assertMapEquals(Map expected, TagMap actual) { // checks entries in both directions to make sure there's full intersection - - for ( Map.Entry expectedEntry: expected.entrySet() ) { + + for (Map.Entry expectedEntry : expected.entrySet()) { TagMap.Entry actualEntry = actual.getEntry(expectedEntry.getKey()); assertNotNull(actualEntry); assertEquals(expectedEntry.getValue(), actualEntry.getValue()); } - - for ( TagMap.Entry actualEntry: actual ) { + + for (TagMap.Entry actualEntry : actual) { Object expectedValue = expected.get(actualEntry.tag()); assertEquals(expectedValue, actualEntry.objectValue()); } } - + static final String randomKey() { return "key-" + ThreadLocalRandom.current().nextInt(NUM_KEYS); } - + static final String randomValue() { return "values-" + ThreadLocalRandom.current().nextInt(); } - + static final String[] randomKeysAndValues() { int numEntries = ThreadLocalRandom.current().nextInt(NUM_KEYS); - + String[] keysAndValues = new String[numEntries << 1]; - for ( int i = 0; i < keysAndValues.length; i += 2 ) { + for (int i = 0; i < keysAndValues.length; i += 2) { keysAndValues[i] = randomKey(); keysAndValues[i + 1] = randomValue(); } return keysAndValues; } - + static final String literal(String str) { return "\"" + str + "\""; } - + static final String literalVarArgs(String... strs) { StringBuilder builder = new StringBuilder(); - for ( String str: strs ) { - if ( builder.length() != 0 ) builder.append(','); + for (String str : strs) { + if (builder.length() != 0) builder.append(','); builder.append(literal(str)); } return builder.toString(); } - + static final Map mapOf(String... keysAndValues) { HashMap map = new HashMap<>(keysAndValues.length >> 1); - for ( int i = 0; i < keysAndValues.length; i += 2 ) { + for (int i = 0; i < keysAndValues.length; i += 2) { String key = keysAndValues[i]; String value = keysAndValues[i + 1]; - + map.put(key, value); } return map; } - + static final TagMap tagMapOf(String... keysAndValues) { TagMap map = new TagMap(); - for ( int i = 0; i < keysAndValues.length; i += 2 ) { + for (int i = 0; i < keysAndValues.length; i += 2) { String key = keysAndValues[i]; String value = keysAndValues[i + 1]; - + map.set(key, value); // map.check(); } return map; } - + static final class TestCase { final List actions; - + TestCase(MapAction... actions) { this.actions = Arrays.asList(actions); } - + TestCase(List actions) { this.actions = actions; } - + public final List actions() { return this.actions; } - + @Override public String toString() { StringBuilder builder = new StringBuilder(); - for ( MapAction action: this.actions ) { + for (MapAction action : this.actions) { builder.append(action).append(',').append('\n'); } return builder.toString(); } } - - static abstract class MapAction { + + abstract static class MapAction { public abstract Object apply(Map mapUnderTest); - + public abstract void verify(Map mapUnderTest); - + public abstract String toString(); } - + static final class Put extends MapAction { final String key; final String value; - + Put(String key, String value) { this.key = key; this.value = value; } - + @Override public Object apply(Map mapUnderTest) { return mapUnderTest.put(this.key, this.value); } - + @Override public void verify(Map mapUnderTest) { assertEquals(this.value, mapUnderTest.get(this.key)); } - + @Override public String toString() { return String.format("put(%s,%s)", literal(this.key), literal(this.value)); } } - + static final class PutAll extends MapAction { final String[] keysAndValues; final Map map; - + PutAll(String... keysAndValues) { this.keysAndValues = keysAndValues; this.map = mapOf(keysAndValues); } - + @Override public Object apply(Map mapUnderTest) { mapUnderTest.putAll(this.map); - + return void.class; } - + @Override public void verify(Map mapUnderTest) { - for ( Map.Entry entry: this.map.entrySet() ) { + for (Map.Entry entry : this.map.entrySet()) { assertEquals(entry.getValue(), mapUnderTest.get(entry.getKey())); } } - + @Override public String toString() { return String.format("putAll(%s)", literalVarArgs(this.keysAndValues)); } } - + static final class PutAllTagMap extends MapAction { final String[] keysAndValues; final TagMap tagMap; - + PutAllTagMap(String... keysAndValues) { this.keysAndValues = keysAndValues; this.tagMap = tagMapOf(keysAndValues); } - + @Override public Object apply(Map mapUnderTest) { mapUnderTest.putAll(this.tagMap); - + return void.class; } - + @Override public void verify(Map mapUnderTest) { - for ( TagMap.Entry entry: this.tagMap ) { + for (TagMap.Entry entry : this.tagMap) { assertEquals(entry.objectValue(), mapUnderTest.get(entry.tag()), "key=" + entry.tag()); } } - + @Override public String toString() { return String.format("putAllTagMap(%s)", literalVarArgs(this.keysAndValues)); } } - + static final class Remove extends MapAction { final String key; - + Remove(String key) { this.key = key; } - + @Override public Object apply(Map mapUnderTest) { return mapUnderTest.remove(this.key); } - + @Override public void verify(Map mapUnderTest) { assertFalse(mapUnderTest.containsKey(this.key)); } - + @Override public String toString() { return String.format("remove(%s)", literal(this.key)); } } - + static final class Clear extends MapAction { static final Clear INSTANCE = new Clear(); - + private Clear() {} - + @Override public Object apply(Map mapUnderTest) { mapUnderTest.clear(); - + return void.class; } - + @Override public void verify(Map mapUnderTest) { assertTrue(mapUnderTest.isEmpty()); } - + @Override public String toString() { return "clear()"; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 4be1b76dcc9..d441ae382cc 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -11,323 +11,322 @@ import java.util.Iterator; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; - import org.junit.jupiter.api.Test; public class TagMapTest { // size is chosen to make sure to stress all types of collisions in the Map static final int MANY_SIZE = 256; - + @Test public void map_put() { TagMap map = new TagMap(); - + Object prev = map.put("foo", "bar"); assertNull(prev); - + assertEntry("foo", "bar", map); - + assertSize(1, map); assertNotEmpty(map); } - + @Test public void clear() { int size = randomSize(); - + TagMap map = createTagMap(size); assertSize(size, map); assertNotEmpty(map); - + map.clear(); assertSize(0, map); assertEmpty(map); } - + @Test public void map_put_replacement() { TagMap map = new TagMap(); Object prev1 = map.put("foo", "bar"); assertNull(prev1); - + assertEntry("foo", "bar", map); assertSize(1, map); assertNotEmpty(map); - + Object prev2 = map.put("foo", "baz"); assertEquals("bar", prev2); - + assertEntry("foo", "baz", map); } - + @Test public void map_remove() { TagMap map = new TagMap(); - + Object prev1 = map.remove("foo"); assertNull(prev1); - + map.put("foo", "bar"); assertEntry("foo", "bar", map); assertSize(1, map); assertNotEmpty(map); - + Object prev2 = map.remove("foo"); assertEquals("bar", prev2); assertSize(0, map); assertEmpty(map); } - + @Test public void freeze() { TagMap map = new TagMap(); map.put("foo", "bar"); - + assertEntry("foo", "bar", map); - + map.freeze(); - - assertFrozen(() -> { - map.remove("foo"); - }); - + + assertFrozen( + () -> { + map.remove("foo"); + }); + assertEntry("foo", "bar", map); } - + @Test public void emptyMap() { TagMap map = TagMap.EMPTY; - + assertSize(0, map); assertEmpty(map); - + assertFrozen(map); } - @Test public void putMany() { int size = randomSize(); TagMap map = createTagMap(size); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), map); } - + assertNotEmpty(map); assertSize(size, map); } - + @Test public void cloneMany() { int size = randomSize(); TagMap orig = createTagMap(size); - + TagMap copy = orig.copy(); orig.clear(); // doing this to make sure that copied isn't modified - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), copy); } } - + @Test public void replaceALot() { int size = randomSize(); TagMap map = createTagMap(size); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { int index = ThreadLocalRandom.current().nextInt(size); - + map.put(key(index), altValue(index)); assertEquals(altValue(index), map.get(key(index))); } } - + @Test public void shareEntry() { TagMap orig = new TagMap(); orig.set("foo", "bar"); - + TagMap dest = new TagMap(); dest.putEntry(orig.getEntry("foo")); - + assertSame(orig.getEntry("foo"), dest.getEntry("foo")); } - + @Test public void putAll() { int size = 67; TagMap orig = createTagMap(size); - + TagMap dest = new TagMap(); - for ( int i = size - 1; i >= 0 ; --i ) { + for (int i = size - 1; i >= 0; --i) { dest.set(key(i), altValue(i)); } - + // This should clobber all the values in dest dest.putAll(orig); - + // assertSize(size, dest); - for ( int i = 0; i < size; ++i ) { + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), dest); } } - + @Test public void removeMany() { int size = randomSize(); TagMap map = createTagMap(size); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), map); } - + assertNotEmpty(map); assertSize(size, map); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { Object removedValue = map.remove(key(i)); assertEquals(value(i), removedValue); - + // not doing exhaustive size checks assertEquals(size - i - 1, map.computeSize()); } - + assertEmpty(map); } - + @Test public void iterator() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(); - for ( TagMap.Entry entry: map ) { + for (TagMap.Entry entry : map) { // makes sure that each key is visited once and only once assertTrue(keys.add(entry.tag())); } - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + @Test public void forEachConsumer() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(size); map.forEach((entry) -> keys.add(entry.tag())); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + @Test public void forEachBiConsumer() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(size); map.forEach(keys, (k, entry) -> k.add(entry.tag())); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + @Test public void forEachTriConsumer() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(size); - map.forEach(keys, "hi", (k, msg, entry) -> keys.add(entry.tag())); - - for ( int i = 0; i < size; ++i ) { + map.forEach(keys, "hi", (k, msg, entry) -> keys.add(entry.tag())); + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + static final int randomSize() { return ThreadLocalRandom.current().nextInt(MANY_SIZE); } - + static final TagMap createTagMap() { return createTagMap(randomSize()); } - + static final TagMap createTagMap(int size) { TagMap map = new TagMap(); - for ( int i = 0; i < size; ++i ) { + for (int i = 0; i < size; ++i) { map.set(key(i), value(i)); } return map; } - + static final String key(int i) { return "key-" + i; } - + static final String value(int i) { return "value-" + i; } - + static final String altValue(int i) { return "alt-value-" + i; } - + static final int count(Iterable iterable) { return count(iterable.iterator()); } - + static final int count(Iterator iter) { int count; - for ( count = 0; iter.hasNext(); ++count ) { + for (count = 0; iter.hasNext(); ++count) { iter.next(); } return count; } - + static final void assertEntry(String key, String value, TagMap map) { TagMap.Entry entry = map.getEntry(key); assertNotNull(entry); - + assertEquals(key, entry.tag()); assertEquals(key, entry.getKey()); - + assertEquals(value, entry.objectValue()); assertTrue(entry.isObject()); assertEquals(value, entry.getValue()); assertEquals(value, entry.stringValue()); - + assertTrue(map.containsKey(key)); assertTrue(map.keySet().contains(key)); - + assertTrue(map.containsValue(value)); assertTrue(map.values().contains(value)); } - + static final void assertSize(int size, TagMap map) { assertEquals(size, map.computeSize()); assertEquals(size, map.size()); @@ -338,32 +337,32 @@ static final void assertSize(int size, TagMap map) { assertEquals(size, count(map.keySet())); assertEquals(size, count(map.values())); } - + static final void assertNotEmpty(TagMap map) { assertFalse(map.checkIfEmpty()); assertFalse(map.isEmpty()); } - + static final void assertEmpty(TagMap map) { assertTrue(map.checkIfEmpty()); assertTrue(map.isEmpty()); } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); } - + static final void assertFrozen(Runnable runnable) { IllegalStateException ex = null; try { runnable.run(); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); From a5e62cff9bfe1c4d0adb1df44d7fe9d439b1b96c Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 13:22:05 -0400 Subject: [PATCH 06/84] Restoring actor.ip check Currently, this checks fails. I believe because of a problem with Mock based tests being overfit to the HashMap implementation. --- .../groovy/com/datadog/appsec/AppSecSystemSpecification.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy index 243c7bd4b20..a92960e0a13 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy @@ -79,7 +79,7 @@ class AppSecSystemSpecification extends DDSpecification { 1 * appSecReqCtx.transferCollectedEvents() >> [Stub(AppSecEvent)] 1 * appSecReqCtx.getRequestHeaders() >> ['foo-bar': ['1.1.1.1']] 1 * appSecReqCtx.getResponseHeaders() >> [:] - // 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') + 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') } void 'throws if the config file is not parseable'() { From b5cb01583de3fad76f9defbf1755f889360367d5 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 14:01:22 -0400 Subject: [PATCH 07/84] Removing commented out tagsSize code --- .../src/main/java/datadog/trace/core/CoreTracer.java | 6 ------ 1 file changed, 6 deletions(-) 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 788dbef8db6..b7f24d4fb77 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 @@ -1718,12 +1718,6 @@ private DDSpanContext buildSpanContext() { boolean mergedTracerTagsNeedsIntercept = traceConfig.mergedTracerTagsNeedsIntercept; final int tagsSize = 0; - // final int tagsSize = - // mergedTracerTags.computeSize() - // + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) - // + (null == coreTags ? 0 : coreTags.size()) - // + (null == rootSpanTags ? 0 : rootSpanTags.size()) - // + (null == contextualTags ? 0 : contextualTags.size()); if (builderRequestContextDataAppSec != null) { requestContextDataAppSec = builderRequestContextDataAppSec; From b13d557b8d4a9460310fd8ab960459ae7bd433fd Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 15:08:03 -0400 Subject: [PATCH 08/84] Adding lots of Javadoc to TagMap --- .../main/java/datadog/trace/api/TagMap.java | 111 ++++++++++++++++-- 1 file changed, 102 insertions(+), 9 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index e988106f298..05a4bc6cbd8 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -32,24 +32,36 @@ public final class TagMap implements Map, Iterable { public static final TagMap EMPTY = createEmpty(); - static final TagMap createEmpty() { + private static final TagMap createEmpty() { return new TagMap().freeze(); } + /** + * Creates a new TagMap.Builder + */ public static final Builder builder() { return new Builder(); } + /** + * Creates a new TagMap.Builder which handles size modifications before expansion + */ public static final Builder builder(int size) { return new Builder(size); } + /** + * Creates a new mutable TagMap that contains the contents of map + */ public static final TagMap fromMap(Map map) { TagMap tagMap = new TagMap(); tagMap.putAll(map); return tagMap; } + /** + * Creates a new immutable TagMap that contains the contents map + */ public static final TagMap fromMapImmutable(Map map) { if (map.isEmpty()) { return TagMap.EMPTY; @@ -107,6 +119,11 @@ public boolean isEmpty() { return this.checkIfEmpty(); } + /** + * Checks if TagMap is empty + * + *

checkIfEmpty is fast but is an O(n) operation. + */ public final boolean checkIfEmpty() { Object[] thisBuckets = this.buckets; @@ -165,11 +182,17 @@ public final Object get(Object tag) { return this.getObject((String) tag); } + /** + * Provides the corresponding entry value as an Object - boxing if necessary + */ public final Object getObject(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.objectValue(); } + /** + * Provides the corresponding entry value as a String - calling toString if necessary + */ public final String getString(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.stringValue(); @@ -200,6 +223,9 @@ public final double getDouble(String tag) { return entry == null ? 0D : entry.doubleValue(); } + /** + * Provides the corresponding Entry object - preferable if the Entry needs to have its type checked + */ public final Entry getEntry(String tag) { Object[] thisBuckets = this.buckets; @@ -227,10 +253,19 @@ public final Object put(String tag, Object value) { return entry == null ? null : entry.objectValue(); } + /** + * Similar to {@link Map#put(Object, Object)}, but returns the prior Entry rather than the prior value + * + * Preferred to put because avoids having to box prior primitive value + */ public final Entry set(String tag, Object value) { return this.putEntry(Entry.newAnyEntry(tag, value)); } + /** + * Similar to {@link TagMap#set(String, Object)} but more efficient when working with CharSequences and Strings. + * Depending on this situation, this methods avoids having to do type resolution later on + */ public final Entry set(String tag, CharSequence value) { return this.putEntry(Entry.newObjectEntry(tag, value)); } @@ -255,6 +290,9 @@ public final Entry set(String tag, double value) { return this.putEntry(Entry.newDoubleEntry(tag, value)); } + /** + * TagMap specific method that places an Entry directly into the TagMap avoiding needing to allocate a new Entry object + */ public final Entry putEntry(Entry newEntry) { this.checkWriteAccess(); @@ -324,6 +362,12 @@ private final void putAll(Entry[] tagEntries, int size) { } } + /** + * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another + * + * This method takes advantage of the consistent Map layout to optimize the handling of each bucket. + * And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the source TagMap + */ public final void putAll(TagMap that) { this.checkWriteAccess(); @@ -468,6 +512,10 @@ public final Object remove(Object tag) { return entry == null ? null : entry.objectValue(); } + /** + * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior value + * This is preferred because it avoids boxing a prior primitive value + */ public final Entry removeEntry(String tag) { this.checkWriteAccess(); @@ -504,12 +552,19 @@ public final Entry removeEntry(String tag) { return null; } + /** + * Returns a mutable copy of this TagMap + */ public final TagMap copy() { TagMap copy = new TagMap(); copy.putAll(this); return copy; } + /** + * Returns an immutable copy of this TagMap + * This method is more efficient than map.copy().freeze() when called on an immutable TagMap + */ public final TagMap immutableCopy() { if (this.frozen) { return this; @@ -523,6 +578,10 @@ public final TagMap immutable() { return new TagMap(this.buckets); } + /** + * Provides an Iterator over the Entry-s of the TagMap + * Equivalent to entrySet().iterator(), but with less allocation + */ @Override public final Iterator iterator() { return new EntryIterator(this); @@ -532,6 +591,10 @@ public final Stream stream() { return StreamSupport.stream(spliterator(), false); } + /** + * Visits each Entry in this TagMap + * This method is more efficient than {@link TagMap#iterator()} + */ public final void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; @@ -550,6 +613,12 @@ public final void forEach(Consumer consumer) { } } + /** + * Version of forEach that takes an extra context object that is passed as the + * first argument to the consumer + * + * The intention is to use this method to avoid using a capturing lambda + */ public final void forEach(T thisObj, BiConsumer consumer) { Object[] thisBuckets = this.buckets; @@ -568,6 +637,12 @@ public final void forEach(T thisObj, BiConsumer con } } + /** + * Version of forEach that takes two extra context objects that are passed as the + * first two argument to the consumer + * + * The intention is to use this method to avoid using a capturing lambda + */ public final void forEach( T thisObj, U otherObj, TriConsumer consumer) { Object[] thisBuckets = this.buckets; @@ -587,21 +662,37 @@ public final void forEach( } } + /** + * Clears the TagMap + */ public final void clear() { this.checkWriteAccess(); Arrays.fill(this.buckets, null); } + /** + * Freeze the TagMap preventing further modification - returns this TagMap + */ public final TagMap freeze() { this.frozen = true; return this; } + /** + * Indicates if this map is frozen + */ public boolean isFrozen() { return this.frozen; } + + /** + * Checks if the TagMap is writable - if not throws {@link IllegalStateException} + */ + public final void checkWriteAccess() { + if (this.frozen) throw new IllegalStateException("TagMap frozen"); + } // final void check() { // Object[] thisBuckets = this.buckets; @@ -638,11 +729,14 @@ public boolean isFrozen() { // } @Override - public String toString() { - return toInternalString(); + public final String toString() { + return toPrettyString(); } - String toPrettyString() { + /** + * Standard toString implementation - output is similar to {@link java.util.HashMap#toString()} + */ + final String toPrettyString() { boolean first = true; StringBuilder builder = new StringBuilder(128); @@ -660,7 +754,10 @@ String toPrettyString() { return builder.toString(); } - String toInternalString() { + /** + * toString that more visibility into the internal structure of TagMap - primarily for deep debugging + */ + final String toInternalString() { Object[] thisBuckets = this.buckets; StringBuilder builder = new StringBuilder(128); @@ -684,10 +781,6 @@ String toInternalString() { return builder.toString(); } - public final void checkWriteAccess() { - if (this.frozen) throw new IllegalStateException("TagMap frozen"); - } - static final int _hash(String tag) { int hash = tag.hashCode(); return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); From 371bf1828074c1ccb338e6a4485379f0d0672321 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 15:26:00 -0400 Subject: [PATCH 09/84] More commenting of TagMap implementation Removed vestiges of the failed hash BloomFilter experiment --- .../main/java/datadog/trace/api/TagMap.java | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 05a4bc6cbd8..bda57dd6a91 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -876,6 +876,11 @@ static final Entry newRemovalEntry(String tag) { } final String tag; + + /* + * hash is stored in line for fast handling of Entry-s coming another Tag + * However, hash is lazily computed using the same trick as {@link java.lang.String}. + */ int hash; // To optimize construction of Entry around boxed primitives and Object entries, @@ -890,6 +895,7 @@ static final Entry newRemovalEntry(String tag) { // However, internally, it is important to remember that this type must be thread safe. // That includes multiple threads racing to resolve an ANY entry at the same time. + // Type and prim cannot use the same trick as hash because during ANY resolution the order of writes is important volatile byte type; volatile long prim; volatile Object obj; @@ -909,6 +915,8 @@ public final String tag() { } int hash() { + // If value of hash read in this thread is zero, then hash is computed. + // hash is not held as a volatile, since this computation can safely be repeated as any time int hash = this.hash; if (hash != 0) return hash; @@ -1555,9 +1563,14 @@ public Entry next() { static final class BucketGroup { static final int LEN = 4; - // int hashFilter = 0; - - // want the hashes together in the same cache line + /* + * To make search operations on BucketGroups fast, the hashes for each entry are held inside + * the BucketGroup. This avoids pointer chasing to inspect each Entry object. + * + * As a further optimization, the hashes are deliberated placed next to each other. + * The intention is that the hashes will all end up in the same cache line, so loading + * one hash effectively loads the others for free. + */ int hash0 = 0; int hash1 = 0; int hash2 = 0; @@ -1578,8 +1591,6 @@ static final class BucketGroup { this.entry0 = entry0; this.prev = prev; - - // this.hashFilter = hash0; } /** New group composed of two entries */ @@ -1589,8 +1600,6 @@ static final class BucketGroup { this.hash1 = hash1; this.entry1 = entry1; - - // this.hashFilter = hash0 | hash1; } /** New group composed of 4 entries - used for cloning */ @@ -1614,8 +1623,6 @@ static final class BucketGroup { this.hash3 = hash3; this.entry3 = entry3; - - // this.hashFilter = hash0 | hash1 | hash2 | hash3; } Entry _entryAt(int index) { @@ -1678,7 +1685,6 @@ boolean isEmptyChain() { boolean _isEmpty() { return (this.hash0 | this.hash1 | this.hash2 | this.hash3) == 0; - // return (this.hashFilter == 0); } BucketGroup findContainingGroupInChain(int hash, String tag) { @@ -1688,10 +1694,6 @@ BucketGroup findContainingGroupInChain(int hash, String tag) { return null; } - // boolean _mayContain(int hash) { - // return ((hash & this.hashFilter) == hash); - // } - Entry findInChain(int hash, String tag) { for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { Entry curEntry = curGroup._find(hash, tag); @@ -1759,28 +1761,22 @@ BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) { // so each unhandled entry from the source group is simply placed in // the same slot in the new group BucketGroup thisNewHashGroup = new BucketGroup(); - int hashFilter = 0; if (!handled0) { thisNewHashGroup.hash0 = thatCurGroup.hash0; thisNewHashGroup.entry0 = thatCurGroup.entry0; - hashFilter |= thatCurGroup.hash0; } if (!handled1) { thisNewHashGroup.hash1 = thatCurGroup.hash1; thisNewHashGroup.entry1 = thatCurGroup.entry1; - hashFilter |= thatCurGroup.hash1; } if (!handled2) { thisNewHashGroup.hash2 = thatCurGroup.hash2; thisNewHashGroup.entry2 = thatCurGroup.entry2; - hashFilter |= thatCurGroup.hash2; } if (!handled3) { thisNewHashGroup.hash3 = thatCurGroup.hash3; thisNewHashGroup.entry3 = thatCurGroup.entry3; - hashFilter |= thatCurGroup.hash3; } - // thisNewHashGroup.hashFilter = hashFilter; thisNewHashGroup.prev = thisNewestHeadGroup; thisNewestHeadGroup = thisNewHashGroup; @@ -1821,7 +1817,6 @@ Entry _replace(int hash, Entry entry) { this.entry3 = entry; } - // no need to update this.hashFilter, since the hash is already included return prevEntry; } @@ -1838,25 +1833,21 @@ boolean _insert(int hash, Entry entry) { this.hash0 = hash; this.entry0 = entry; - // this.hashFilter |= hash; inserted = true; } else if (this.hash1 == 0) { this.hash1 = hash; this.entry1 = entry; - // this.hashFilter |= hash; inserted = true; } else if (this.hash2 == 0) { this.hash2 = hash; this.entry2 = entry; - // this.hashFilter |= hash; inserted = true; } else if (this.hash3 == 0) { this.hash3 = hash; this.entry3 = entry; - // this.hashFilter |= hash; inserted = true; } return inserted; From 32d5849b09336721fca02c89549ad24440668a3e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 17:04:14 -0400 Subject: [PATCH 10/84] More commenting of the TagMap implementation --- .../main/java/datadog/trace/api/TagMap.java | 65 +++++++++++++++---- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index bda57dd6a91..6efbe8b9d62 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -5,6 +5,7 @@ import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; @@ -15,21 +16,56 @@ import java.util.stream.StreamSupport; /** - * A super simple hash map designed for... - fast copy from one map to another - compatibility with - * Builder idioms - building small maps as fast as possible - storing primitives without boxing - - * minimal memory footprint + * A super simple hash map designed for... + *

    + *
  • fast copy from one map to another + *
  • compatibility with Builder idioms + *
  • building small maps as fast as possible + *
  • storing primitives without boxing + *
  • minimal memory footprint + *
* *

This is mainly accomplished by using immutable entry objects that can reference an object or a * primitive. By using immutable entries, the entry objects can be shared between builders & maps * freely. * - *

This map lacks some features of a regular java.util.Map... - Entry object mutation - size - * tracking - falls back to computeSize - manipulating Map through the entrySet() or values() + *

This map lacks some features of a regular java.util.Map... + *

    + *
  • Entry object mutation + *
  • size tracking - falls back to computeSize + *
  • manipulating Map through the entrySet() or values() + *
* - *

Also lacks features designed for handling large maps... - bucket array expansion - adaptive - * collision + *

Also lacks features designed for handling large maps... + *

    + *
  • bucket array expansion + *
  • adaptive collision + *
+ */ + +/* + * For memory efficiency, TagMap uses a rather complicated bucket system. + *

+ * When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly. + *

+ * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain + * form a link list to handle collisions. + *

+ * Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries. + * But a BucketGroup is only formed when a collision occurs to keep allocation low in the common case of no collisions. + *

+ * For efficiency, BucketGroups are a fixed size, so when a BucketGroup fills up another BucketGroup is formed + * to hold the additional Entry-s. And the BucketGroup-s are connected via a linked list instead of the Entry-s. + *

+ * This does introduce some inefficiencies when Entry-s are removed. + * In the current system, given that removals are rare, BucketGroups are never consolidated. + * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be + * removed from the collision chain. */ public final class TagMap implements Map, Iterable { + /** + * Immutable empty TagMap - similar to {@link Collections#emptyMap()} + */ public static final TagMap EMPTY = createEmpty(); private static final TagMap createEmpty() { @@ -1555,10 +1591,12 @@ public Entry next() { } /** - * BucketGroup is compromise for performance over a linked list or array - linked list - would - * prevent TagEntry-s from being immutable and would limit sharing opportunities - array - - * wouldn't be able to store hashes close together - parallel arrays (one for hashes & another for - * entries) would require more allocation + * BucketGroup is compromise for performance over a linked list or array + * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities + * - arrays - wouldn't be able to store hashes close together + * - parallel arrays (one for hashes & another for entries) would require more allocation + * + * BucketGroups are */ static final class BucketGroup { static final int LEN = 4; @@ -1566,10 +1604,13 @@ static final class BucketGroup { /* * To make search operations on BucketGroups fast, the hashes for each entry are held inside * the BucketGroup. This avoids pointer chasing to inspect each Entry object. - * + *

* As a further optimization, the hashes are deliberated placed next to each other. * The intention is that the hashes will all end up in the same cache line, so loading * one hash effectively loads the others for free. + *

+ * A hash of zero indicates an available slot, the hashes passed to BucketGroup must be "adjusted" + * hashes which can never be zero. The zero handling is done by TagMap#hash. */ int hash0 = 0; int hash1 = 0; From c252e7b950df2acd81606672460434f6f267498b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 11:46:08 -0400 Subject: [PATCH 11/84] Adding tests for BucketGroup First phase of adding tests for BucketGroup - in this first phase, just adding tests for directly manipulating the BucketGroup - not the BucketGroup chain --- .../trace/api/TagMapBucketGroupTest.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java new file mode 100644 index 00000000000..0c33fdfd94b --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -0,0 +1,149 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import datadog.trace.api.TagMap.BucketGroup; + +public class TagMapBucketGroupTest { + @Test + public void newGroup() { + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertEquals(firstHash, group._hashAt(0)); + assertEquals(firstEntry, group._entryAt(0)); + + assertEquals(secondEntry.hash(), group._hashAt(1)); + assertEquals(secondEntry, group._entryAt(1)); + + assertFalse(group._isEmpty()); + assertFalse(group.isEmptyChain()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + } + + @Test + public void _insert() { + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); + + TagMap.Entry newEntry = TagMap.Entry.newAnyEntry("baz", "lorem ipsum"); + int newHash = newEntry.hash(); + + assertTrue(group._insert(newHash, newEntry)); + + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry newEntry2 = TagMap.Entry.newDoubleEntry("new", 3.1415926535D); + int newHash2 = newEntry2.hash(); + + assertTrue(group._insert(newHash2, newEntry2)); + + assertContainsDirectly(newEntry2, group); + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry overflowEntry = TagMap.Entry.newDoubleEntry("overflow", 2.718281828D); + int overflowHash = overflowEntry.hash(); + assertFalse(group._insert(overflowHash, overflowEntry)); + + assertDoesntContainDirectly(overflowEntry, group); + } + + @Test + public void _replace() { + TagMap.Entry origEntry = TagMap.Entry.newIntEntry("replaceable", 0xDD06); + TagMap.Entry otherEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int origHash = origEntry.hash(); + int otherHash = otherEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); + assertContainsDirectly(origEntry, group); + assertContainsDirectly(otherEntry, group); + + TagMap.Entry replacementEntry = TagMap.Entry.newBooleanEntry("replaceable", true); + int replacementHash = replacementEntry.hash(); + assertEquals(replacementHash, origHash); + + TagMap.Entry priorEntry = group._replace(origHash, replacementEntry); + assertSame(priorEntry, origEntry); + + assertContainsDirectly(replacementEntry, group); + assertDoesntContainDirectly(priorEntry, group); + + TagMap.Entry dneEntry = TagMap.Entry.newAnyEntry("dne", "not present"); + int dneHash = dneEntry.hash(); + + assertNull(group._replace(dneHash, dneEntry)); + assertDoesntContainDirectly(dneEntry, group); + } + + @Test + public void _remove() { + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("first", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("second", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertFalse(group._isEmpty()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + assertSame(firstEntry, group._remove(firstHash, "first")); + + assertDoesntContainDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + assertFalse(group._isEmpty()); + + assertSame(secondEntry, group._remove(secondHash, "second")); + assertDoesntContainDirectly(secondEntry, group); + + assertTrue(group._isEmpty()); + } + + static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { + int hash = entry.hash(); + String tag = entry.tag(); + + assertSame(entry, group._find(hash, tag)); + + assertSame(entry, group.findInChain(hash, tag)); + assertSame(group, group.findContainingGroupInChain(hash, tag)); + } + + static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { + for ( int i = 0; i < BucketGroup.LEN; ++i ) { + assertNotSame(entry, group._entryAt(i)); + } + } +} From 468aee61df7c6ccdaf0d25496a8a20e20d533732 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 15:43:28 -0400 Subject: [PATCH 12/84] BucketGroup chain tests Adding tests that cover modifications to the chain (linked list) of BucketGroups - basic construction - entry replacement - entry insertion - entry removal More tests to come --- .../trace/api/TagMapBucketGroupTest.java | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index 0c33fdfd94b..f94a48ee2bf 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertSame; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -129,6 +130,162 @@ public void _remove() { assertDoesntContainDirectly(secondEntry, group); assertTrue(group._isEmpty()); + } + + @Test + public void groupChaining() { + int startingIndex = 10; + BucketGroup firstGroup = fullGroup(startingIndex); + + for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + assertChainContainsTag(tag(startingIndex + offset), firstGroup); + } + + TagMap.Entry newEntry = TagMap.Entry.newObjectEntry("new", "new"); + int newHash = newEntry.hash(); + + // This is a test of the process used by TagMap#put + assertNull(firstGroup._replace(newHash, newEntry)); + assertFalse(firstGroup._insert(newHash, newEntry)); + assertDoesntContainDirectly(newEntry, firstGroup); + + BucketGroup newHeadGroup = new BucketGroup(newHash, newEntry, firstGroup); + assertContainsDirectly(newEntry, newHeadGroup); + assertSame(firstGroup, newHeadGroup.prev); + + assertChainContainsTag("new", newHeadGroup); + for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); + } + } + + @Test + public void removeInChain() { + BucketGroup firstGroup = fullGroup(10); + BucketGroup headGroup = fullGroup(20, firstGroup); + + for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + assertChainContainsTag(tag(10, offset), headGroup); + assertChainContainsTag(tag(20, offset), headGroup); + } + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + String firstRemovedTag = tag(10, 1); + int firstRemovedHash = TagMap._hash(firstRemovedTag); + + BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); + assertSame(firstContainingGroup, firstGroup); + assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); + + assertChainDoesntContainTag(firstRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 1); + + String secondRemovedTag = tag(20, 2); + int secondRemovedHash = TagMap._hash(secondRemovedTag); + + BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); + assertSame(secondContainingGroup, headGroup); + assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); + + assertChainDoesntContainTag(secondRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + } + + @Test + public void replaceInChain() { + BucketGroup firstGroup = fullGroup(10); + BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); + assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); + assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + } + + @Test + public void insertInChain() { + // set-up a chain with some gaps in it + BucketGroup firstGroup = fullGroup(10); + BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + String firstHoleTag = tag(10, 1); + int firstHoleHash = TagMap._hash(firstHoleTag); + firstGroup._remove(firstHoleHash, firstHoleTag); + + String secondHoleTag = tag(20, 2); + int secondHoleHash = TagMap._hash(secondHoleTag); + headGroup._remove(secondHoleHash, secondHoleTag); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + + String firstNewTag = "new-tag-0"; + TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); + int firstNewHash = firstNewEntry.hash(); + + assertTrue(headGroup.insertInChain(firstNewHash, firstNewEntry)); + assertChainContainsTag(firstNewTag, headGroup); + + String secondNewTag = "new-tag-1"; + TagMap.Entry secondNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int secondNewHash = secondNewEntry.hash(); + + assertTrue(headGroup.insertInChain(secondNewHash, secondNewEntry)); + assertChainContainsTag(secondNewTag, headGroup); + + String thirdNewTag = "new-tag-2"; + TagMap.Entry thirdNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int thirdNewHash = secondNewEntry.hash(); + + assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); + assertChainDoesntContainTag(thirdNewTag, headGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + } + + static final BucketGroup fullGroup(int startingIndex) { + TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); + + BucketGroup group = new BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + for ( int offset = 2; offset < BucketGroup.LEN; ++offset ) { + TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); + group._insert(anotherEntry.hash(), anotherEntry); + } + return group; + } + + static final BucketGroup fullGroup(int startingIndex, BucketGroup prev) { + BucketGroup group = fullGroup(startingIndex); + group.prev = prev; + return group; + } + + static final String tag(int startingIndex, int offset) { + return tag(startingIndex + offset); + } + + static final String tag(int i) { + return "tag-" + i; + } + + static final String value(int startingIndex, int offset) { + return value(startingIndex + offset); + } + + static final String value(int i) { + return "value-i"; } static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { @@ -146,4 +303,14 @@ static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup g assertNotSame(entry, group._entryAt(i)); } } + + static void assertChainContainsTag(String tag, TagMap.BucketGroup group) { + int hash = TagMap._hash(tag); + assertNotNull(group.findInChain(hash, tag)); + } + + static void assertChainDoesntContainTag(String tag, TagMap.BucketGroup group) { + int hash = TagMap._hash(tag); + assertNull(group.findInChain(hash, tag)); + } } From e58d8eeaff52e8d0ac1ccbbbce208d0da05ff02c Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 16:27:37 -0400 Subject: [PATCH 13/84] More BucketGroup tests Checking group removal from chain --- .../trace/api/TagMapBucketGroupTest.java | 113 +++++++++++++----- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index f94a48ee2bf..6d5dcc4b295 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -10,8 +10,6 @@ import org.junit.jupiter.api.Test; -import datadog.trace.api.TagMap.BucketGroup; - public class TagMapBucketGroupTest { @Test public void newGroup() { @@ -135,9 +133,9 @@ public void _remove() { @Test public void groupChaining() { int startingIndex = 10; - BucketGroup firstGroup = fullGroup(startingIndex); + TagMap.BucketGroup firstGroup = fullGroup(startingIndex); - for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { assertChainContainsTag(tag(startingIndex + offset), firstGroup); } @@ -149,76 +147,76 @@ public void groupChaining() { assertFalse(firstGroup._insert(newHash, newEntry)); assertDoesntContainDirectly(newEntry, firstGroup); - BucketGroup newHeadGroup = new BucketGroup(newHash, newEntry, firstGroup); + TagMap.BucketGroup newHeadGroup = new TagMap.BucketGroup(newHash, newEntry, firstGroup); assertContainsDirectly(newEntry, newHeadGroup); assertSame(firstGroup, newHeadGroup.prev); assertChainContainsTag("new", newHeadGroup); - for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); } } @Test public void removeInChain() { - BucketGroup firstGroup = fullGroup(10); - BucketGroup headGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { assertChainContainsTag(tag(10, offset), headGroup); assertChainContainsTag(tag(20, offset), headGroup); } - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); String firstRemovedTag = tag(10, 1); int firstRemovedHash = TagMap._hash(firstRemovedTag); - BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); + TagMap.BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); assertSame(firstContainingGroup, firstGroup); assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); assertChainDoesntContainTag(firstRemovedTag, headGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 1); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 1); String secondRemovedTag = tag(20, 2); int secondRemovedHash = TagMap._hash(secondRemovedTag); - BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); + TagMap.BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); assertSame(secondContainingGroup, headGroup); assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); assertChainDoesntContainTag(secondRemovedTag, headGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); } @Test public void replaceInChain() { - BucketGroup firstGroup = fullGroup(10); - BucketGroup headGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); } @Test public void insertInChain() { // set-up a chain with some gaps in it - BucketGroup firstGroup = fullGroup(10); - BucketGroup headGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); String firstHoleTag = tag(10, 1); int firstHoleHash = TagMap._hash(firstHoleTag); @@ -228,7 +226,7 @@ public void insertInChain() { int secondHoleHash = TagMap._hash(secondHoleTag); headGroup._remove(secondHoleHash, secondHoleTag); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); String firstNewTag = "new-tag-0"; TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); @@ -251,23 +249,62 @@ public void insertInChain() { assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); assertChainDoesntContainTag(thirdNewTag, headGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + } + + @Test + public void cloneChain() { + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup headGroup = fullGroup(30, secondGroup); + + TagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); + TagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; + TagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; + + assertGroupContentsStrictEquals(headGroup, clonedHeadGroup); + assertGroupContentsStrictEquals(secondGroup, clonedSecondGroup); + assertGroupContentsStrictEquals(firstGroup, clonedFirstGroup); + } + + @Test + public void removeGroupInChain() { + TagMap.BucketGroup tailGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); + TagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); + TagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); + TagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); + assertChain(headGroup, fourthGroup, thirdGroup, secondGroup, tailGroup); + + // need to test group removal - at head, middle, and tail of the chain + + // middle + assertSame(headGroup, headGroup.removeGroupInChain(thirdGroup)); + assertChain(headGroup, fourthGroup, secondGroup, tailGroup); + + // tail + assertSame(headGroup, headGroup.removeGroupInChain(tailGroup)); + assertChain(headGroup, fourthGroup, secondGroup); + + // head + assertSame(fourthGroup, headGroup.removeGroupInChain(headGroup)); + assertChain(fourthGroup, secondGroup); } - static final BucketGroup fullGroup(int startingIndex) { + static final TagMap.BucketGroup fullGroup(int startingIndex) { TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); - BucketGroup group = new BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); - for ( int offset = 2; offset < BucketGroup.LEN; ++offset ) { + TagMap.BucketGroup group = new TagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + for ( int offset = 2; offset < TagMap.BucketGroup.LEN; ++offset ) { TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); group._insert(anotherEntry.hash(), anotherEntry); } return group; } - static final BucketGroup fullGroup(int startingIndex, BucketGroup prev) { - BucketGroup group = fullGroup(startingIndex); + static final TagMap.BucketGroup fullGroup(int startingIndex, TagMap.BucketGroup prev) { + TagMap.BucketGroup group = fullGroup(startingIndex); group.prev = prev; return group; } @@ -299,7 +336,7 @@ static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) } static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { - for ( int i = 0; i < BucketGroup.LEN; ++i ) { + for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { assertNotSame(entry, group._entryAt(i)); } } @@ -313,4 +350,20 @@ static void assertChainDoesntContainTag(String tag, TagMap.BucketGroup group) { int hash = TagMap._hash(tag); assertNull(group.findInChain(hash, tag)); } + + static void assertGroupContentsStrictEquals(TagMap.BucketGroup expected, TagMap.BucketGroup actual) { + for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { + assertEquals(expected._hashAt(i), actual._hashAt(i)); + assertSame(expected._entryAt(i), actual._entryAt(i)); + } + } + + static void assertChain(TagMap.BucketGroup... chain) { + TagMap.BucketGroup cur; + int index; + for ( cur = chain[0], index = 0; cur != null; cur = cur.prev, ++index ) { + assertSame(chain[index], cur); + } + assertEquals(chain.length, index); + } } From 93c8d08b4fe1c1f313879dc48cef9fe502f6d4d1 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 16:32:36 -0400 Subject: [PATCH 14/84] spotless & doc clean-up --- .../main/java/datadog/trace/api/TagMap.java | 169 +++-- .../trace/api/TagMapBucketGroupTest.java | 607 +++++++++--------- 2 files changed, 386 insertions(+), 390 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 6efbe8b9d62..0911189fdb1 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -17,29 +17,32 @@ /** * A super simple hash map designed for... + * *

    - *
  • fast copy from one map to another - *
  • compatibility with Builder idioms - *
  • building small maps as fast as possible - *
  • storing primitives without boxing - *
  • minimal memory footprint + *
  • fast copy from one map to another + *
  • compatibility with Builder idioms + *
  • building small maps as fast as possible + *
  • storing primitives without boxing + *
  • minimal memory footprint *
* *

This is mainly accomplished by using immutable entry objects that can reference an object or a * primitive. By using immutable entries, the entry objects can be shared between builders & maps * freely. * - *

This map lacks some features of a regular java.util.Map... + *

This map lacks some features of a regular java.util.Map... + * *

    - *
  • Entry object mutation - *
  • size tracking - falls back to computeSize - *
  • manipulating Map through the entrySet() or values() + *
  • Entry object mutation + *
  • size tracking - falls back to computeSize + *
  • manipulating Map through the entrySet() or values() *
* *

Also lacks features designed for handling large maps... + * *

    - *
  • bucket array expansion - *
  • adaptive collision + *
  • bucket array expansion + *
  • adaptive collision *
*/ @@ -48,56 +51,46 @@ *

* When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly. *

- * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain + * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain * form a link list to handle collisions. *

* Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries. * But a BucketGroup is only formed when a collision occurs to keep allocation low in the common case of no collisions. *

- * For efficiency, BucketGroups are a fixed size, so when a BucketGroup fills up another BucketGroup is formed + * For efficiency, BucketGroups are a fixed size, so when a BucketGroup fills up another BucketGroup is formed * to hold the additional Entry-s. And the BucketGroup-s are connected via a linked list instead of the Entry-s. *

* This does introduce some inefficiencies when Entry-s are removed. * In the current system, given that removals are rare, BucketGroups are never consolidated. - * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be + * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be * removed from the collision chain. */ public final class TagMap implements Map, Iterable { - /** - * Immutable empty TagMap - similar to {@link Collections#emptyMap()} - */ + /** Immutable empty TagMap - similar to {@link Collections#emptyMap()} */ public static final TagMap EMPTY = createEmpty(); private static final TagMap createEmpty() { return new TagMap().freeze(); } - /** - * Creates a new TagMap.Builder - */ + /** Creates a new TagMap.Builder */ public static final Builder builder() { return new Builder(); } - /** - * Creates a new TagMap.Builder which handles size modifications before expansion - */ + /** Creates a new TagMap.Builder which handles size modifications before expansion */ public static final Builder builder(int size) { return new Builder(size); } - /** - * Creates a new mutable TagMap that contains the contents of map - */ + /** Creates a new mutable TagMap that contains the contents of map */ public static final TagMap fromMap(Map map) { TagMap tagMap = new TagMap(); tagMap.putAll(map); return tagMap; } - /** - * Creates a new immutable TagMap that contains the contents map - */ + /** Creates a new immutable TagMap that contains the contents map */ public static final TagMap fromMapImmutable(Map map) { if (map.isEmpty()) { return TagMap.EMPTY; @@ -218,17 +211,13 @@ public final Object get(Object tag) { return this.getObject((String) tag); } - /** - * Provides the corresponding entry value as an Object - boxing if necessary - */ + /** Provides the corresponding entry value as an Object - boxing if necessary */ public final Object getObject(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.objectValue(); } - /** - * Provides the corresponding entry value as a String - calling toString if necessary - */ + /** Provides the corresponding entry value as a String - calling toString if necessary */ public final String getString(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.stringValue(); @@ -260,7 +249,8 @@ public final double getDouble(String tag) { } /** - * Provides the corresponding Entry object - preferable if the Entry needs to have its type checked + * Provides the corresponding Entry object - preferable if the Entry needs to have its type + * checked */ public final Entry getEntry(String tag) { Object[] thisBuckets = this.buckets; @@ -290,17 +280,19 @@ public final Object put(String tag, Object value) { } /** - * Similar to {@link Map#put(Object, Object)}, but returns the prior Entry rather than the prior value - * - * Preferred to put because avoids having to box prior primitive value + * Similar to {@link Map#put(Object, Object)}, but returns the prior Entry rather than the prior + * value + * + *

Preferred to put because avoids having to box prior primitive value */ public final Entry set(String tag, Object value) { return this.putEntry(Entry.newAnyEntry(tag, value)); } /** - * Similar to {@link TagMap#set(String, Object)} but more efficient when working with CharSequences and Strings. - * Depending on this situation, this methods avoids having to do type resolution later on + * Similar to {@link TagMap#set(String, Object)} but more efficient when working with + * CharSequences and Strings. Depending on this situation, this methods avoids having to do type + * resolution later on */ public final Entry set(String tag, CharSequence value) { return this.putEntry(Entry.newObjectEntry(tag, value)); @@ -327,7 +319,8 @@ public final Entry set(String tag, double value) { } /** - * TagMap specific method that places an Entry directly into the TagMap avoiding needing to allocate a new Entry object + * TagMap specific method that places an Entry directly into the TagMap avoiding needing to + * allocate a new Entry object */ public final Entry putEntry(Entry newEntry) { this.checkWriteAccess(); @@ -400,9 +393,10 @@ private final void putAll(Entry[] tagEntries, int size) { /** * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another - * - * This method takes advantage of the consistent Map layout to optimize the handling of each bucket. - * And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the source TagMap + * + *

This method takes advantage of the consistent Map layout to optimize the handling of each + * bucket. And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the + * source TagMap */ public final void putAll(TagMap that) { this.checkWriteAccess(); @@ -549,8 +543,8 @@ public final Object remove(Object tag) { } /** - * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior value - * This is preferred because it avoids boxing a prior primitive value + * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior + * value This is preferred because it avoids boxing a prior primitive value */ public final Entry removeEntry(String tag) { this.checkWriteAccess(); @@ -588,9 +582,7 @@ public final Entry removeEntry(String tag) { return null; } - /** - * Returns a mutable copy of this TagMap - */ + /** Returns a mutable copy of this TagMap */ public final TagMap copy() { TagMap copy = new TagMap(); copy.putAll(this); @@ -598,8 +590,8 @@ public final TagMap copy() { } /** - * Returns an immutable copy of this TagMap - * This method is more efficient than map.copy().freeze() when called on an immutable TagMap + * Returns an immutable copy of this TagMap This method is more efficient than + * map.copy().freeze() when called on an immutable TagMap */ public final TagMap immutableCopy() { if (this.frozen) { @@ -615,8 +607,8 @@ public final TagMap immutable() { } /** - * Provides an Iterator over the Entry-s of the TagMap - * Equivalent to entrySet().iterator(), but with less allocation + * Provides an Iterator over the Entry-s of the TagMap Equivalent to entrySet().iterator() + * , but with less allocation */ @Override public final Iterator iterator() { @@ -628,8 +620,7 @@ public final Stream stream() { } /** - * Visits each Entry in this TagMap - * This method is more efficient than {@link TagMap#iterator()} + * Visits each Entry in this TagMap This method is more efficient than {@link TagMap#iterator()} */ public final void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; @@ -650,10 +641,10 @@ public final void forEach(Consumer consumer) { } /** - * Version of forEach that takes an extra context object that is passed as the - * first argument to the consumer - * - * The intention is to use this method to avoid using a capturing lambda + * Version of forEach that takes an extra context object that is passed as the first argument to + * the consumer + * + *

The intention is to use this method to avoid using a capturing lambda */ public final void forEach(T thisObj, BiConsumer consumer) { Object[] thisBuckets = this.buckets; @@ -674,10 +665,10 @@ public final void forEach(T thisObj, BiConsumer con } /** - * Version of forEach that takes two extra context objects that are passed as the - * first two argument to the consumer - * - * The intention is to use this method to avoid using a capturing lambda + * Version of forEach that takes two extra context objects that are passed as the first two + * argument to the consumer + * + *

The intention is to use this method to avoid using a capturing lambda */ public final void forEach( T thisObj, U otherObj, TriConsumer consumer) { @@ -698,34 +689,26 @@ public final void forEach( } } - /** - * Clears the TagMap - */ + /** Clears the TagMap */ public final void clear() { this.checkWriteAccess(); Arrays.fill(this.buckets, null); } - /** - * Freeze the TagMap preventing further modification - returns this TagMap - */ + /** Freeze the TagMap preventing further modification - returns this TagMap */ public final TagMap freeze() { this.frozen = true; return this; } - /** - * Indicates if this map is frozen - */ + /** Indicates if this map is frozen */ public boolean isFrozen() { return this.frozen; } - - /** - * Checks if the TagMap is writable - if not throws {@link IllegalStateException} - */ + + /** Checks if the TagMap is writable - if not throws {@link IllegalStateException} */ public final void checkWriteAccess() { if (this.frozen) throw new IllegalStateException("TagMap frozen"); } @@ -791,7 +774,8 @@ final String toPrettyString() { } /** - * toString that more visibility into the internal structure of TagMap - primarily for deep debugging + * toString that more visibility into the internal structure of TagMap - primarily for deep + * debugging */ final String toInternalString() { Object[] thisBuckets = this.buckets; @@ -912,7 +896,7 @@ static final Entry newRemovalEntry(String tag) { } final String tag; - + /* * hash is stored in line for fast handling of Entry-s coming another Tag * However, hash is lazily computed using the same trick as {@link java.lang.String}. @@ -931,7 +915,8 @@ static final Entry newRemovalEntry(String tag) { // However, internally, it is important to remember that this type must be thread safe. // That includes multiple threads racing to resolve an ANY entry at the same time. - // Type and prim cannot use the same trick as hash because during ANY resolution the order of writes is important + // Type and prim cannot use the same trick as hash because during ANY resolution the order of + // writes is important volatile byte type; volatile long prim; volatile Object obj; @@ -1591,26 +1576,28 @@ public Entry next() { } /** - * BucketGroup is compromise for performance over a linked list or array - * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities - * - arrays - wouldn't be able to store hashes close together - * - parallel arrays (one for hashes & another for entries) would require more allocation - * - * BucketGroups are + * BucketGroup is a compromise for performance over a linked list or array + * + *

    + *
  • linked list - would prevent TagEntry-s from being immutable and would limit sharing + * opportunities + *
  • arrays - wouldn't be able to store hashes close together + *
  • parallel arrays (one for hashes & another for entries) would require more allocation + *
*/ static final class BucketGroup { static final int LEN = 4; /* - * To make search operations on BucketGroups fast, the hashes for each entry are held inside + * To make search operations on BucketGroups fast, the hashes for each entry are held inside * the BucketGroup. This avoids pointer chasing to inspect each Entry object. *

- * As a further optimization, the hashes are deliberated placed next to each other. - * The intention is that the hashes will all end up in the same cache line, so loading + * As a further optimization, the hashes are deliberately placed next to each other. + * The intention is that the hashes will all end up in the same cache line, so loading * one hash effectively loads the others for free. *

- * A hash of zero indicates an available slot, the hashes passed to BucketGroup must be "adjusted" - * hashes which can never be zero. The zero handling is done by TagMap#hash. + * A hash of zero indicates an available slot, the hashes passed to BucketGroup must be "adjusted" + * hashes which can never be zero. The zero handling is done by TagMap#_hash. */ int hash0 = 0; int hash1 = 0; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index 6d5dcc4b295..cad674ffeca 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -13,357 +13,366 @@ public class TagMapBucketGroupTest { @Test public void newGroup() { - TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); - - int firstHash = firstEntry.hash(); - int secondHash = secondEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup( - firstHash, firstEntry, - secondHash, secondEntry); - - assertEquals(firstHash, group._hashAt(0)); - assertEquals(firstEntry, group._entryAt(0)); - - assertEquals(secondEntry.hash(), group._hashAt(1)); - assertEquals(secondEntry, group._entryAt(1)); - - assertFalse(group._isEmpty()); - assertFalse(group.isEmptyChain()); - - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = + new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertEquals(firstHash, group._hashAt(0)); + assertEquals(firstEntry, group._entryAt(0)); + + assertEquals(secondEntry.hash(), group._hashAt(1)); + assertEquals(secondEntry, group._entryAt(1)); + + assertFalse(group._isEmpty()); + assertFalse(group.isEmptyChain()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); } - + @Test public void _insert() { - TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); - - int firstHash = firstEntry.hash(); - int secondHash = secondEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); - - TagMap.Entry newEntry = TagMap.Entry.newAnyEntry("baz", "lorem ipsum"); - int newHash = newEntry.hash(); - - assertTrue(group._insert(newHash, newEntry)); - - assertContainsDirectly(newEntry, group); - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - - TagMap.Entry newEntry2 = TagMap.Entry.newDoubleEntry("new", 3.1415926535D); - int newHash2 = newEntry2.hash(); - - assertTrue(group._insert(newHash2, newEntry2)); - - assertContainsDirectly(newEntry2, group); - assertContainsDirectly(newEntry, group); - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - - TagMap.Entry overflowEntry = TagMap.Entry.newDoubleEntry("overflow", 2.718281828D); - int overflowHash = overflowEntry.hash(); - assertFalse(group._insert(overflowHash, overflowEntry)); - - assertDoesntContainDirectly(overflowEntry, group); + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = + new TagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); + + TagMap.Entry newEntry = TagMap.Entry.newAnyEntry("baz", "lorem ipsum"); + int newHash = newEntry.hash(); + + assertTrue(group._insert(newHash, newEntry)); + + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry newEntry2 = TagMap.Entry.newDoubleEntry("new", 3.1415926535D); + int newHash2 = newEntry2.hash(); + + assertTrue(group._insert(newHash2, newEntry2)); + + assertContainsDirectly(newEntry2, group); + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry overflowEntry = TagMap.Entry.newDoubleEntry("overflow", 2.718281828D); + int overflowHash = overflowEntry.hash(); + assertFalse(group._insert(overflowHash, overflowEntry)); + + assertDoesntContainDirectly(overflowEntry, group); } - + @Test public void _replace() { - TagMap.Entry origEntry = TagMap.Entry.newIntEntry("replaceable", 0xDD06); - TagMap.Entry otherEntry = TagMap.Entry.newObjectEntry("bar", "quux"); - - int origHash = origEntry.hash(); - int otherHash = otherEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); - assertContainsDirectly(origEntry, group); - assertContainsDirectly(otherEntry, group); - - TagMap.Entry replacementEntry = TagMap.Entry.newBooleanEntry("replaceable", true); - int replacementHash = replacementEntry.hash(); - assertEquals(replacementHash, origHash); - - TagMap.Entry priorEntry = group._replace(origHash, replacementEntry); - assertSame(priorEntry, origEntry); - - assertContainsDirectly(replacementEntry, group); - assertDoesntContainDirectly(priorEntry, group); - - TagMap.Entry dneEntry = TagMap.Entry.newAnyEntry("dne", "not present"); - int dneHash = dneEntry.hash(); - - assertNull(group._replace(dneHash, dneEntry)); - assertDoesntContainDirectly(dneEntry, group); + TagMap.Entry origEntry = TagMap.Entry.newIntEntry("replaceable", 0xDD06); + TagMap.Entry otherEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int origHash = origEntry.hash(); + int otherHash = otherEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); + assertContainsDirectly(origEntry, group); + assertContainsDirectly(otherEntry, group); + + TagMap.Entry replacementEntry = TagMap.Entry.newBooleanEntry("replaceable", true); + int replacementHash = replacementEntry.hash(); + assertEquals(replacementHash, origHash); + + TagMap.Entry priorEntry = group._replace(origHash, replacementEntry); + assertSame(priorEntry, origEntry); + + assertContainsDirectly(replacementEntry, group); + assertDoesntContainDirectly(priorEntry, group); + + TagMap.Entry dneEntry = TagMap.Entry.newAnyEntry("dne", "not present"); + int dneHash = dneEntry.hash(); + + assertNull(group._replace(dneHash, dneEntry)); + assertDoesntContainDirectly(dneEntry, group); } - + @Test public void _remove() { - TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("first", 0xDD06); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("second", "quux"); - - int firstHash = firstEntry.hash(); - int secondHash = secondEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup( - firstHash, firstEntry, - secondHash, secondEntry); - - assertFalse(group._isEmpty()); - - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - - assertSame(firstEntry, group._remove(firstHash, "first")); - - assertDoesntContainDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - assertFalse(group._isEmpty()); - - assertSame(secondEntry, group._remove(secondHash, "second")); - assertDoesntContainDirectly(secondEntry, group); - - assertTrue(group._isEmpty()); - } - + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("first", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("second", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = + new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertFalse(group._isEmpty()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + assertSame(firstEntry, group._remove(firstHash, "first")); + + assertDoesntContainDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + assertFalse(group._isEmpty()); + + assertSame(secondEntry, group._remove(secondHash, "second")); + assertDoesntContainDirectly(secondEntry, group); + + assertTrue(group._isEmpty()); + } + @Test public void groupChaining() { - int startingIndex = 10; - TagMap.BucketGroup firstGroup = fullGroup(startingIndex); - - for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { - assertChainContainsTag(tag(startingIndex + offset), firstGroup); - } - - TagMap.Entry newEntry = TagMap.Entry.newObjectEntry("new", "new"); - int newHash = newEntry.hash(); - - // This is a test of the process used by TagMap#put - assertNull(firstGroup._replace(newHash, newEntry)); - assertFalse(firstGroup._insert(newHash, newEntry)); - assertDoesntContainDirectly(newEntry, firstGroup); - - TagMap.BucketGroup newHeadGroup = new TagMap.BucketGroup(newHash, newEntry, firstGroup); - assertContainsDirectly(newEntry, newHeadGroup); - assertSame(firstGroup, newHeadGroup.prev); - - assertChainContainsTag("new", newHeadGroup); - for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { - assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); - } + int startingIndex = 10; + TagMap.BucketGroup firstGroup = fullGroup(startingIndex); + + for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + assertChainContainsTag(tag(startingIndex + offset), firstGroup); + } + + TagMap.Entry newEntry = TagMap.Entry.newObjectEntry("new", "new"); + int newHash = newEntry.hash(); + + // This is a test of the process used by TagMap#put + assertNull(firstGroup._replace(newHash, newEntry)); + assertFalse(firstGroup._insert(newHash, newEntry)); + assertDoesntContainDirectly(newEntry, firstGroup); + + TagMap.BucketGroup newHeadGroup = new TagMap.BucketGroup(newHash, newEntry, firstGroup); + assertContainsDirectly(newEntry, newHeadGroup); + assertSame(firstGroup, newHeadGroup.prev); + + assertChainContainsTag("new", newHeadGroup); + for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); + } } - + @Test public void removeInChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - - for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { - assertChainContainsTag(tag(10, offset), headGroup); - assertChainContainsTag(tag(20, offset), headGroup); - } - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - String firstRemovedTag = tag(10, 1); - int firstRemovedHash = TagMap._hash(firstRemovedTag); - - TagMap.BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); - assertSame(firstContainingGroup, firstGroup); - assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); - - assertChainDoesntContainTag(firstRemovedTag, headGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 1); - - String secondRemovedTag = tag(20, 2); - int secondRemovedHash = TagMap._hash(secondRemovedTag); - - TagMap.BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); - assertSame(secondContainingGroup, headGroup); - assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); - - assertChainDoesntContainTag(secondRemovedTag, headGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + + for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + assertChainContainsTag(tag(10, offset), headGroup); + assertChainContainsTag(tag(20, offset), headGroup); + } + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + String firstRemovedTag = tag(10, 1); + int firstRemovedHash = TagMap._hash(firstRemovedTag); + + TagMap.BucketGroup firstContainingGroup = + headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); + assertSame(firstContainingGroup, firstGroup); + assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); + + assertChainDoesntContainTag(firstRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 1); + + String secondRemovedTag = tag(20, 2); + int secondRemovedHash = TagMap._hash(secondRemovedTag); + + TagMap.BucketGroup secondContainingGroup = + headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); + assertSame(secondContainingGroup, headGroup); + assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); + + assertChainDoesntContainTag(secondRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); } - + @Test public void replaceInChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); - assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); - assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); + assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); + assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); } - + @Test public void insertInChain() { - // set-up a chain with some gaps in it - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - String firstHoleTag = tag(10, 1); - int firstHoleHash = TagMap._hash(firstHoleTag); - firstGroup._remove(firstHoleHash, firstHoleTag); - - String secondHoleTag = tag(20, 2); - int secondHoleHash = TagMap._hash(secondHoleTag); - headGroup._remove(secondHoleHash, secondHoleTag); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); - - String firstNewTag = "new-tag-0"; - TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); - int firstNewHash = firstNewEntry.hash(); - - assertTrue(headGroup.insertInChain(firstNewHash, firstNewEntry)); - assertChainContainsTag(firstNewTag, headGroup); - - String secondNewTag = "new-tag-1"; - TagMap.Entry secondNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); - int secondNewHash = secondNewEntry.hash(); - - assertTrue(headGroup.insertInChain(secondNewHash, secondNewEntry)); - assertChainContainsTag(secondNewTag, headGroup); - - String thirdNewTag = "new-tag-2"; - TagMap.Entry thirdNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); - int thirdNewHash = secondNewEntry.hash(); - - assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); - assertChainDoesntContainTag(thirdNewTag, headGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + // set-up a chain with some gaps in it + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + String firstHoleTag = tag(10, 1); + int firstHoleHash = TagMap._hash(firstHoleTag); + firstGroup._remove(firstHoleHash, firstHoleTag); + + String secondHoleTag = tag(20, 2); + int secondHoleHash = TagMap._hash(secondHoleTag); + headGroup._remove(secondHoleHash, secondHoleTag); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); + + String firstNewTag = "new-tag-0"; + TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); + int firstNewHash = firstNewEntry.hash(); + + assertTrue(headGroup.insertInChain(firstNewHash, firstNewEntry)); + assertChainContainsTag(firstNewTag, headGroup); + + String secondNewTag = "new-tag-1"; + TagMap.Entry secondNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int secondNewHash = secondNewEntry.hash(); + + assertTrue(headGroup.insertInChain(secondNewHash, secondNewEntry)); + assertChainContainsTag(secondNewTag, headGroup); + + String thirdNewTag = "new-tag-2"; + TagMap.Entry thirdNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int thirdNewHash = secondNewEntry.hash(); + + assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); + assertChainDoesntContainTag(thirdNewTag, headGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); } - + @Test public void cloneChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); - TagMap.BucketGroup headGroup = fullGroup(30, secondGroup); - - TagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); - TagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; - TagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; - - assertGroupContentsStrictEquals(headGroup, clonedHeadGroup); - assertGroupContentsStrictEquals(secondGroup, clonedSecondGroup); - assertGroupContentsStrictEquals(firstGroup, clonedFirstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup headGroup = fullGroup(30, secondGroup); + + TagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); + TagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; + TagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; + + assertGroupContentsStrictEquals(headGroup, clonedHeadGroup); + assertGroupContentsStrictEquals(secondGroup, clonedSecondGroup); + assertGroupContentsStrictEquals(firstGroup, clonedFirstGroup); } - + @Test public void removeGroupInChain() { - TagMap.BucketGroup tailGroup = fullGroup(10); - TagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); - TagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); - TagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); - TagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); - assertChain(headGroup, fourthGroup, thirdGroup, secondGroup, tailGroup); - - // need to test group removal - at head, middle, and tail of the chain - - // middle - assertSame(headGroup, headGroup.removeGroupInChain(thirdGroup)); - assertChain(headGroup, fourthGroup, secondGroup, tailGroup); - - // tail - assertSame(headGroup, headGroup.removeGroupInChain(tailGroup)); - assertChain(headGroup, fourthGroup, secondGroup); - - // head - assertSame(fourthGroup, headGroup.removeGroupInChain(headGroup)); - assertChain(fourthGroup, secondGroup); + TagMap.BucketGroup tailGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); + TagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); + TagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); + TagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); + assertChain(headGroup, fourthGroup, thirdGroup, secondGroup, tailGroup); + + // need to test group removal - at head, middle, and tail of the chain + + // middle + assertSame(headGroup, headGroup.removeGroupInChain(thirdGroup)); + assertChain(headGroup, fourthGroup, secondGroup, tailGroup); + + // tail + assertSame(headGroup, headGroup.removeGroupInChain(tailGroup)); + assertChain(headGroup, fourthGroup, secondGroup); + + // head + assertSame(fourthGroup, headGroup.removeGroupInChain(headGroup)); + assertChain(fourthGroup, secondGroup); } - + static final TagMap.BucketGroup fullGroup(int startingIndex) { - TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); - - TagMap.BucketGroup group = new TagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); - for ( int offset = 2; offset < TagMap.BucketGroup.LEN; ++offset ) { - TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); - group._insert(anotherEntry.hash(), anotherEntry); - } - return group; + TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); + TagMap.Entry secondEntry = + TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); + + TagMap.BucketGroup group = + new TagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + for (int offset = 2; offset < TagMap.BucketGroup.LEN; ++offset) { + TagMap.Entry anotherEntry = + TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); + group._insert(anotherEntry.hash(), anotherEntry); + } + return group; } - + static final TagMap.BucketGroup fullGroup(int startingIndex, TagMap.BucketGroup prev) { - TagMap.BucketGroup group = fullGroup(startingIndex); + TagMap.BucketGroup group = fullGroup(startingIndex); group.prev = prev; return group; } - + static final String tag(int startingIndex, int offset) { - return tag(startingIndex + offset); + return tag(startingIndex + offset); } - + static final String tag(int i) { - return "tag-" + i; + return "tag-" + i; } - + static final String value(int startingIndex, int offset) { - return value(startingIndex + offset); + return value(startingIndex + offset); } - + static final String value(int i) { - return "value-i"; + return "value-i"; } - + static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { - int hash = entry.hash(); - String tag = entry.tag(); - - assertSame(entry, group._find(hash, tag)); - - assertSame(entry, group.findInChain(hash, tag)); - assertSame(group, group.findContainingGroupInChain(hash, tag)); + int hash = entry.hash(); + String tag = entry.tag(); + + assertSame(entry, group._find(hash, tag)); + + assertSame(entry, group.findInChain(hash, tag)); + assertSame(group, group.findContainingGroupInChain(hash, tag)); } - + static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { - for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { + for (int i = 0; i < TagMap.BucketGroup.LEN; ++i) { assertNotSame(entry, group._entryAt(i)); } } - + static void assertChainContainsTag(String tag, TagMap.BucketGroup group) { - int hash = TagMap._hash(tag); - assertNotNull(group.findInChain(hash, tag)); + int hash = TagMap._hash(tag); + assertNotNull(group.findInChain(hash, tag)); } - + static void assertChainDoesntContainTag(String tag, TagMap.BucketGroup group) { - int hash = TagMap._hash(tag); - assertNull(group.findInChain(hash, tag)); + int hash = TagMap._hash(tag); + assertNull(group.findInChain(hash, tag)); } - - static void assertGroupContentsStrictEquals(TagMap.BucketGroup expected, TagMap.BucketGroup actual) { - for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { - assertEquals(expected._hashAt(i), actual._hashAt(i)); - assertSame(expected._entryAt(i), actual._entryAt(i)); - } + + static void assertGroupContentsStrictEquals( + TagMap.BucketGroup expected, TagMap.BucketGroup actual) { + for (int i = 0; i < TagMap.BucketGroup.LEN; ++i) { + assertEquals(expected._hashAt(i), actual._hashAt(i)); + assertSame(expected._entryAt(i), actual._entryAt(i)); + } } - + static void assertChain(TagMap.BucketGroup... chain) { - TagMap.BucketGroup cur; - int index; - for ( cur = chain[0], index = 0; cur != null; cur = cur.prev, ++index ) { - assertSame(chain[index], cur); - } - assertEquals(chain.length, index); + TagMap.BucketGroup cur; + int index; + for (cur = chain[0], index = 0; cur != null; cur = cur.prev, ++index) { + assertSame(chain[index], cur); + } + assertEquals(chain.length, index); } } From b489ce9f6b0bc08088b5c8ac00c4ed8b8aab50ab Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 16:49:08 -0400 Subject: [PATCH 15/84] Tried default case per analysis tool - it was worse --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 0911189fdb1..563c0dee549 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1666,6 +1666,8 @@ Entry _entryAt(int index) { case 3: return this.entry3; + + // Do not use default case, that creates a 5% cost on entry handling } return null; @@ -1684,8 +1686,10 @@ int _hashAt(int index) { case 3: return this.hash3; + + // Do not use default case, that creates a 5% cost on entry handling } - + return 0; } From 6b01cb3e0f7565a48b75816231b59e7753e468ab Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 17:19:30 -0400 Subject: [PATCH 16/84] More comments to clarify optimizations --- .../main/java/datadog/trace/core/DDSpanContext.java | 3 +++ .../src/main/java/datadog/trace/api/TagMap.java | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) 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 4efee4c0e6a..edd2bb56ed7 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 @@ -756,6 +756,9 @@ void setAllTags(final TagMap map, boolean needsIntercept) { synchronized (unsafeTags) { if (needsIntercept) { + // forEach out-performs the iterator of TagMap + // Taking advantage of ability to pass through other context arguments + // to avoid using a capturing lambda map.forEach( this, traceCollector.getTracer().getTagInterceptor(), diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 563c0dee549..5b44cc63d7e 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1666,8 +1666,8 @@ Entry _entryAt(int index) { case 3: return this.entry3; - - // Do not use default case, that creates a 5% cost on entry handling + + // Do not use default case, that creates a 5% cost on entry handling } return null; @@ -1686,10 +1686,10 @@ int _hashAt(int index) { case 3: return this.hash3; - - // Do not use default case, that creates a 5% cost on entry handling + + // Do not use default case, that creates a 5% cost on entry handling } - + return 0; } From 4d36dba2d0923fcc04412ca5734e9037017a6c80 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 25 Mar 2025 15:47:34 -0400 Subject: [PATCH 17/84] Fixing potential bug with SpanBuilder tag removal clobbering entry from another tag source Added a TagMap.Builder#smartRemove method smartRemove only adds to the Builder ledger if there's already corresponding put earlier in the ledger While this method is O(n), removes are assumed to be rare - and this is preferable to complicated handling in CoreSpanBuilder#setAllTags --- .../java/datadog/trace/core/CoreTracer.java | 6 +- .../main/java/datadog/trace/api/TagMap.java | 97 ++++++++++++++++--- .../datadog/trace/api/TagMapBuilderTest.java | 73 ++++++++------ 3 files changed, 136 insertions(+), 40 deletions(-) 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 b7f24d4fb77..9b1e1ed3d7c 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 @@ -1495,7 +1495,11 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { - tagBuilder.remove(tag); + // DQH - Use of smartRemove is important to avoid clobbering entries added by another map + // smartRemove only records the removal if a prior matching put has already occurred in the builder + // smartRemove is O(n) but since removes are rare, this is preferable to a more complicated + // implementation in setAll + tagBuilder.smartRemove(tag); } else { tagBuilder.put(tag, value); } diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 5b44cc63d7e..d9a827cf9cb 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1,5 +1,6 @@ package datadog.trace.api; +import datadog.trace.api.TagMap.Entry; import datadog.trace.api.function.TriConsumer; import java.util.AbstractCollection; import java.util.AbstractSet; @@ -1371,18 +1372,32 @@ private static final double prim2Double(long prim) { } } + /** + * TagMap.Builder maintains a ledger of Entry objects that can be used to construct a TagMap. + *

Since TagMap.Entry objects are immutable and shared, the cost of the TagMap.Builder is negligible + * as compared to most Builders. + *

TagMap.Builder's ledger can also be read directly to ensure that Entry-s are processed in order. + * In this way, TagMap.Builder can serve as a stand-in for a LinkedHashMap when Entry-s are not inserted + * more than once. + */ public static final class Builder implements Iterable { private Entry[] entries; private int nextPos = 0; + private boolean containsRemovals = false; - private Builder() { + Builder() { this(8); } - private Builder(int size) { + Builder(int size) { this.entries = new Entry[size]; } + /** + * Indicates if the ledger is completely empty + * + * build map still return an empty Map even if this method returns true. + */ public final boolean isDefinitelyEmpty() { return (this.nextPos == 0); } @@ -1396,48 +1411,106 @@ public final boolean isDefinitelyEmpty() { public final int estimateSize() { return this.nextPos; } + + /** + * Indicates if the ledger contains any removal entries + * @return + */ + public final boolean containsRemovals() { + return this.containsRemovals; + } public final Builder put(String tag, Object value) { - return this.put(Entry.newAnyEntry(tag, value)); + return this.recordEntry(Entry.newAnyEntry(tag, value)); } public final Builder put(String tag, CharSequence value) { - return this.put(Entry.newObjectEntry(tag, value)); + return this.recordEntry(Entry.newObjectEntry(tag, value)); } public final Builder put(String tag, boolean value) { - return this.put(Entry.newBooleanEntry(tag, value)); + return this.recordEntry(Entry.newBooleanEntry(tag, value)); } public final Builder put(String tag, int value) { - return this.put(Entry.newIntEntry(tag, value)); + return this.recordEntry(Entry.newIntEntry(tag, value)); } public final Builder put(String tag, long value) { - return this.put(Entry.newLongEntry(tag, value)); + return this.recordEntry(Entry.newLongEntry(tag, value)); } public final Builder put(String tag, float value) { - return this.put(Entry.newFloatEntry(tag, value)); + return this.recordEntry(Entry.newFloatEntry(tag, value)); } public final Builder put(String tag, double value) { - return this.put(Entry.newDoubleEntry(tag, value)); + return this.recordEntry(Entry.newDoubleEntry(tag, value)); } - - public final Builder remove(String tag) { - return this.put(Entry.newRemovalEntry(tag)); + + public final Builder uncheckedPut(Entry entry) { + return this.recordEntry(entry); } public final Builder put(Entry entry) { + this.recordChange(entry); + this.containsRemovals |= entry.isRemoval(); + return this; + } + + /** + * Records a removal Entry in the ledger + */ + public final Builder remove(String tag) { + return this.recordRemoval(Entry.newRemovalEntry(tag)); + } + + private final Builder recordEntry(Entry entry) { + this.recordChange(entry); + return this; + } + + private final Builder recordRemoval(Entry entry) { + this.recordChange(entry); + this.containsRemovals = true; + + return this; + } + + private final void recordChange(Entry entry) { if (this.nextPos >= this.entries.length) { this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); } this.entries[this.nextPos++] = entry; + } + + /** + * Smarter (but more expensive) version of {@link Builder#remove(String)} + * A removal entry is only added to the ledger if there is a prior insertion entry in the ledger + * + * NOTE: This method does require an O(n) traversal of the ledger + */ + public final Builder smartRemove(String tag) { + if ( this.contains(tag) ) { + this.remove(tag); + } return this; } + + private final boolean contains(String tag) { + Entry[] entries = this.entries; + + // min is to clamp, so ArrayBoundsCheckElimination optimization works + for ( int i = 0; i < Math.min(this.nextPos, entries.length); ++i ) { + if ( entries[i].matches(tag) ) return true; + } + return false; + } + /** + * Resets the ledger + */ public final void reset() { Arrays.fill(this.entries, null); this.nextPos = 0; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index 16ad7b68226..908eb3a31af 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -2,90 +2,109 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; public class TagMapBuilderTest { static final int SIZE = 32; - + @Test public void buildMutable() { TagMap.Builder builder = TagMap.builder(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.build(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + // just proving that the map is mutable map.set(key(1000), value(1000)); } - + @Test public void buildImmutable() { TagMap.Builder builder = TagMap.builder(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.buildImmutable(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + assertFrozen(map); } - + @Test public void buildWithRemoves() { TagMap.Builder builder = TagMap.builder(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { builder.put(key(i), value(i)); } - - for (int i = 0; i < SIZE; i += 2) { + + for ( int i = 0; i < SIZE; i += 2 ) { builder.remove(key(i)); } TagMap map = builder.build(); - for (int i = 0; i < SIZE; ++i) { - if ((i % 2) == 0) { + for ( int i = 0; i < SIZE; ++i ) { + if ( (i % 2) == 0 ) { assertNull(map.getString(key(i))); } else { assertEquals(value(i), map.getString(key(i))); } } } - + + @Test + public void smartRemoval_existingCase() { + TagMap.Builder builder = TagMap.builder(); + builder.put("foo", "bar"); + builder.smartRemove("foo"); + + assertTrue(builder.containsRemovals()); + } + + @Test + public void smartRemoval_missingCase() { + TagMap.Builder builder = TagMap.builder(); + builder.smartRemove("foo"); + + assertFalse(builder.containsRemovals()); + } + @Test public void reset() { TagMap.Builder builder = TagMap.builder(2); - + builder.put(key(0), value(0)); TagMap map0 = builder.build(); - + builder.reset(); - + builder.put(key(1), value(1)); TagMap map1 = builder.build(); - + assertEquals(value(0), map0.getString(key(0))); assertNull(map1.getString(key(0))); - + assertNull(map0.getString(key(1))); assertEquals(value(1), map1.getString(key(1))); } - + static final String key(int i) { return "key-" + i; } @@ -93,12 +112,12 @@ static final String key(int i) { static final String value(int i) { return "value-" + i; } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch (IllegalStateException e) { + } catch ( IllegalStateException e ) { ex = e; } assertNotNull(ex); From b9238c80be4f3dbd9c53e665d86ea5d2ddce8992 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 25 Mar 2025 15:48:13 -0400 Subject: [PATCH 18/84] spotless --- .../java/datadog/trace/core/CoreTracer.java | 11 +-- .../main/java/datadog/trace/api/TagMap.java | 59 +++++++------- .../datadog/trace/api/TagMapBuilderTest.java | 78 +++++++++---------- 3 files changed, 74 insertions(+), 74 deletions(-) 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 9b1e1ed3d7c..ef533c565fd 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 @@ -1495,11 +1495,12 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { - // DQH - Use of smartRemove is important to avoid clobbering entries added by another map - // smartRemove only records the removal if a prior matching put has already occurred in the builder - // smartRemove is O(n) but since removes are rare, this is preferable to a more complicated - // implementation in setAll - tagBuilder.smartRemove(tag); + // DQH - Use of smartRemove is important to avoid clobbering entries added by another map + // smartRemove only records the removal if a prior matching put has already occurred in the + // builder + // smartRemove is O(n) but since removes are rare, this is preferable to a more complicated + // implementation in setAll + tagBuilder.smartRemove(tag); } else { tagBuilder.put(tag, value); } diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index d9a827cf9cb..6b9f42b7905 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1374,11 +1374,13 @@ private static final double prim2Double(long prim) { /** * TagMap.Builder maintains a ledger of Entry objects that can be used to construct a TagMap. - *

Since TagMap.Entry objects are immutable and shared, the cost of the TagMap.Builder is negligible - * as compared to most Builders. - *

TagMap.Builder's ledger can also be read directly to ensure that Entry-s are processed in order. - * In this way, TagMap.Builder can serve as a stand-in for a LinkedHashMap when Entry-s are not inserted - * more than once. + * + *

Since TagMap.Entry objects are immutable and shared, the cost of the TagMap.Builder is + * negligible as compared to most Builders. + * + *

TagMap.Builder's ledger can also be read directly to ensure that Entry-s are processed in + * order. In this way, TagMap.Builder can serve as a stand-in for a LinkedHashMap when Entry-s are + * not inserted more than once. */ public static final class Builder implements Iterable { private Entry[] entries; @@ -1395,8 +1397,8 @@ public static final class Builder implements Iterable { /** * Indicates if the ledger is completely empty - * - * build map still return an empty Map even if this method returns true. + * + *

build map still return an empty Map even if this method returns true. */ public final boolean isDefinitelyEmpty() { return (this.nextPos == 0); @@ -1411,9 +1413,10 @@ public final boolean isDefinitelyEmpty() { public final int estimateSize() { return this.nextPos; } - + /** * Indicates if the ledger contains any removal entries + * * @return */ public final boolean containsRemovals() { @@ -1447,7 +1450,7 @@ public final Builder put(String tag, float value) { public final Builder put(String tag, double value) { return this.recordEntry(Entry.newDoubleEntry(tag, value)); } - + public final Builder uncheckedPut(Entry entry) { return this.recordEntry(entry); } @@ -1458,25 +1461,23 @@ public final Builder put(Entry entry) { return this; } - /** - * Records a removal Entry in the ledger - */ + /** Records a removal Entry in the ledger */ public final Builder remove(String tag) { return this.recordRemoval(Entry.newRemovalEntry(tag)); } - + private final Builder recordEntry(Entry entry) { this.recordChange(entry); return this; } - + private final Builder recordRemoval(Entry entry) { this.recordChange(entry); this.containsRemovals = true; - + return this; } - + private final void recordChange(Entry entry) { if (this.nextPos >= this.entries.length) { this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); @@ -1484,33 +1485,31 @@ private final void recordChange(Entry entry) { this.entries[this.nextPos++] = entry; } - + /** - * Smarter (but more expensive) version of {@link Builder#remove(String)} - * A removal entry is only added to the ledger if there is a prior insertion entry in the ledger - * - * NOTE: This method does require an O(n) traversal of the ledger + * Smarter (but more expensive) version of {@link Builder#remove(String)} A removal entry is + * only added to the ledger if there is a prior insertion entry in the ledger + * + *

NOTE: This method does require an O(n) traversal of the ledger */ public final Builder smartRemove(String tag) { - if ( this.contains(tag) ) { - this.remove(tag); + if (this.contains(tag)) { + this.remove(tag); } return this; } - + private final boolean contains(String tag) { Entry[] entries = this.entries; - + // min is to clamp, so ArrayBoundsCheckElimination optimization works - for ( int i = 0; i < Math.min(this.nextPos, entries.length); ++i ) { - if ( entries[i].matches(tag) ) return true; + for (int i = 0; i < Math.min(this.nextPos, entries.length); ++i) { + if (entries[i].matches(tag)) return true; } return false; } - /** - * Resets the ledger - */ + /** Resets the ledger */ public final void reset() { Arrays.fill(this.entries, null); this.nextPos = 0; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index 908eb3a31af..a9be9881533 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -1,110 +1,110 @@ package datadog.trace.api; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; public class TagMapBuilderTest { static final int SIZE = 32; - + @Test public void buildMutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + // just proving that the map is mutable map.set(key(1000), value(1000)); } - + @Test public void buildImmutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.buildImmutable(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + assertFrozen(map); } - + @Test public void buildWithRemoves() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - - for ( int i = 0; i < SIZE; i += 2 ) { + + for (int i = 0; i < SIZE; i += 2) { builder.remove(key(i)); } TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { - if ( (i % 2) == 0 ) { + for (int i = 0; i < SIZE; ++i) { + if ((i % 2) == 0) { assertNull(map.getString(key(i))); } else { assertEquals(value(i), map.getString(key(i))); } } } - + @Test public void smartRemoval_existingCase() { - TagMap.Builder builder = TagMap.builder(); - builder.put("foo", "bar"); - builder.smartRemove("foo"); - - assertTrue(builder.containsRemovals()); + TagMap.Builder builder = TagMap.builder(); + builder.put("foo", "bar"); + builder.smartRemove("foo"); + + assertTrue(builder.containsRemovals()); } - + @Test public void smartRemoval_missingCase() { - TagMap.Builder builder = TagMap.builder(); - builder.smartRemove("foo"); - - assertFalse(builder.containsRemovals()); + TagMap.Builder builder = TagMap.builder(); + builder.smartRemove("foo"); + + assertFalse(builder.containsRemovals()); } - + @Test public void reset() { TagMap.Builder builder = TagMap.builder(2); - + builder.put(key(0), value(0)); TagMap map0 = builder.build(); - + builder.reset(); - + builder.put(key(1), value(1)); TagMap map1 = builder.build(); - + assertEquals(value(0), map0.getString(key(0))); assertNull(map1.getString(key(0))); - + assertNull(map0.getString(key(1))); assertEquals(value(1), map1.getString(key(1))); } - + static final String key(int i) { return "key-" + i; } @@ -112,12 +112,12 @@ static final String key(int i) { static final String value(int i) { return "value-" + i; } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); From 00adef8ee5955acc8a5ed3bca5a4c0a766f22d84 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 11:26:09 -0400 Subject: [PATCH 19/84] Progress on fixing failing tests Almost all the tests are now passing The primary problem was that SpanInfo.getTags is now expected to return a TagMap. The tests using Mock-s were using regular Maps instead Updated those tests to wrap the Map literal in TagMap.fromMap --- .../datadog/iast/sink/HstsMissingHeaderModuleTest.groovy | 5 +++-- .../iast/sink/InsecureAuthProtocolModuleTest.groovy | 5 +++-- .../iast/sink/XContentTypeOptionsModuleTest.groovy | 9 +++++---- .../com/datadog/appsec/AppSecSystemSpecification.groovy | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy index bc5f5ef305a..84c2858f654 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy @@ -7,6 +7,7 @@ import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType import com.datadog.iast.overhead.Operation import com.datadog.iast.overhead.OverheadController +import datadog.trace.api.TagMap import datadog.trace.api.gateway.Flow import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.internal.TraceSegment @@ -45,10 +46,10 @@ class HstsMissingHeaderModuleTest extends IastModuleImplTestBase { final handler = new RequestEndedHandler(dependencies) ctx.xForwardedProto = 'https' ctx.contentType = "text/html" - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.url': 'https://localhost/a', 'http.status_code': 200i - ] + ]) when: def flow = handler.apply(reqCtx, span) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy index 882f533e6f7..646778e34b7 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy @@ -5,6 +5,7 @@ import com.datadog.iast.Reporter import com.datadog.iast.RequestEndedHandler import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import datadog.trace.api.TagMap import datadog.trace.api.gateway.Flow import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.iast.sink.InsecureAuthProtocolModule @@ -42,9 +43,9 @@ class InsecureAuthProtocolModuleTest extends IastModuleImplTestBase{ given: final handler = new RequestEndedHandler(dependencies) ctx.authorization = value - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.status_code': status_code - ] + ]) when: def flow = handler.apply(reqCtx, span) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy index 7224ca49a24..c7f0eae4198 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy @@ -5,6 +5,7 @@ import com.datadog.iast.Reporter import com.datadog.iast.RequestEndedHandler import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import datadog.trace.api.TagMap import datadog.trace.api.gateway.Flow import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.internal.TraceSegment @@ -33,9 +34,9 @@ class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { given: final handler = new RequestEndedHandler(dependencies) ctx.contentType = "text/html" - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.status_code': 200i - ] + ]) when: def flow = handler.apply(reqCtx, span) @@ -56,10 +57,10 @@ class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { final handler = new RequestEndedHandler(dependencies) ctx.xForwardedProto = 'https' ctx.contentType = "text/html" - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.url': url, 'http.status_code': status - ] + ]) when: def flow = handler.apply(reqCtx, span) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy index a92960e0a13..52292a28a33 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy @@ -13,6 +13,7 @@ import datadog.remoteconfig.ConfigurationEndListener import datadog.remoteconfig.ConfigurationPoller import datadog.remoteconfig.Product import datadog.trace.api.Config +import datadog.trace.api.TagMap import datadog.trace.api.internal.TraceSegment import datadog.trace.api.gateway.Flow import datadog.trace.api.gateway.IGSpanInfo @@ -72,7 +73,7 @@ class AppSecSystemSpecification extends DDSpecification { requestEndedCB.apply(requestContext, span) then: - 1 * span.getTags() >> ['http.client_ip':'1.1.1.1'] + 1 * span.getTags() >> TagMap.fromMap(['http.client_ip':'1.1.1.1']) 1 * subService.registerCallback(EVENTS.requestEnded(), _) >> { requestEndedCB = it[1]; null } 1 * requestContext.getData(RequestContextSlot.APPSEC) >> appSecReqCtx 1 * requestContext.traceSegment >> traceSegment From 4ae0c5275a7cd3d6cbb23bc7824bcc720126a5c2 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 11:53:08 -0400 Subject: [PATCH 20/84] Fixed the last getTags() mock that I missed previously --- .../datadog/appsec/gateway/GatewayBridgeSpecification.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy index e5285983003..54e0ff0fb19 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy @@ -8,6 +8,7 @@ import com.datadog.appsec.event.data.DataBundle import com.datadog.appsec.event.data.KnownAddresses import com.datadog.appsec.report.AppSecEvent import com.datadog.appsec.report.AppSecEventWrapper +import datadog.trace.api.TagMap import datadog.trace.api.ProductTraceSource import datadog.trace.api.UserIdCollectionMode import datadog.trace.api.appsec.LoginEventCallback @@ -160,7 +161,7 @@ class GatewayBridgeSpecification extends DDSpecification { def flow = requestEndedCB.apply(mockCtx, spanInfo) then: - 1 * spanInfo.getTags() >> ['http.client_ip': '1.1.1.1'] + 1 * spanInfo.getTags() >> TagMap.fromMap(['http.client_ip': '1.1.1.1']) 1 * mockAppSecCtx.transferCollectedEvents() >> [event] 1 * mockAppSecCtx.peerAddress >> '2001::1' 1 * mockAppSecCtx.close(false) From 376d62336ae9fb87dd1f44ee4892d4e7e9446a90 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 12:30:53 -0400 Subject: [PATCH 21/84] spotless --- .../datadog/trace/core/tagprocessor/SpanPointersProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 20e7fb795ce..35d33b1ed0c 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 @@ -160,4 +160,4 @@ private static AgentSpanLink buildSpanPointer(String hash, String ptrKind) { return SpanLink.from(noopSpanContext(), DEFAULT_FLAGS, "", attributes); } -} \ No newline at end of file +} From 55beae516676e16f276c42008e454a63ea34d2b9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 15:03:14 -0400 Subject: [PATCH 22/84] Fixing errant merge which left imports to recently removed classes --- .../datadog/appsec/gateway/GatewayBridgeSpecification.groovy | 3 --- 1 file changed, 3 deletions(-) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy index 0f84d8dee6c..dcbc2863262 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy @@ -9,9 +9,6 @@ import com.datadog.appsec.event.data.KnownAddresses import com.datadog.appsec.report.AppSecEvent import com.datadog.appsec.report.AppSecEventWrapper import datadog.trace.api.TagMap -import datadog.trace.api.ProductTraceSource -import datadog.trace.api.UserIdCollectionMode -import datadog.trace.api.appsec.LoginEventCallback import datadog.trace.api.function.TriConsumer import datadog.trace.api.function.TriFunction import datadog.trace.api.gateway.BlockResponseFunction From 096a565d35a5627d0db636c6e232709f0a33a22e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 15:47:35 -0400 Subject: [PATCH 23/84] Fixing a failing test that was added by the latest merge? --- .../datadog/appsec/gateway/GatewayBridgeSpecification.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy index dcbc2863262..6e9725e7b91 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy @@ -190,7 +190,7 @@ class GatewayBridgeSpecification extends DDSpecification { then: 1 * mockAppSecCtx.transferCollectedEvents() >> [Stub(AppSecEvent)] - 1 * spanInfo.getTags() >> ['http.client_ip': '8.8.8.8'] + 1 * spanInfo.getTags() >> TagMap.fromMap(['http.client_ip': '8.8.8.8']) 1 * traceSegment.setTagTop('actor.ip', '8.8.8.8') } From b208ce19fcab5865417d340aa29aaeb0dc215568 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 18:01:57 -0400 Subject: [PATCH 24/84] Removing unnecessary comment --- .../datadog/trace/bootstrap/instrumentation/api/TagContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index d91b41ce72e..d833e5760bf 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -168,7 +168,6 @@ public String getCustomIpHeader() { } public final TagMap getTags() { - // DQH - Because of the lazy in putTag, this method effectively returns an immutable map return (this.tags == null) ? TagMap.EMPTY : this.tags; } From 616e55dd1dfc00c31a4a1121d2d9abe20f3bfe6f Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 19:00:00 -0400 Subject: [PATCH 25/84] Fixing infinite recursion found by SpotBugs --- .../src/main/java/datadog/trace/core/DDSpanContext.java | 4 ++++ 1 file changed, 4 insertions(+) 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 edd2bb56ed7..7f482d11f31 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 @@ -748,6 +748,10 @@ public void setTag(final String tag, final Object value) { } } } + + void setAllTags(final TagMap map) { + setAllTags(map, true); + } void setAllTags(final TagMap map, boolean needsIntercept) { if (map == null) { From 06c5575178931ebe971d293a48590d7da27304cc Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 19:08:16 -0400 Subject: [PATCH 26/84] spotless --- .../src/main/java/datadog/trace/core/DDSpanContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7f482d11f31..84c2c439874 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 @@ -748,9 +748,9 @@ public void setTag(final String tag, final Object value) { } } } - + void setAllTags(final TagMap map) { - setAllTags(map, true); + setAllTags(map, true); } void setAllTags(final TagMap map, boolean needsIntercept) { From ce2792a91176d062bdc6fff2707e14e36deec737 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 19:51:33 -0400 Subject: [PATCH 27/84] quieting spotbugs --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 6b9f42b7905..b899eec036e 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1068,6 +1068,10 @@ public final Object objectValue() { case DOUBLE: this.obj = prim2Double(this.prim); break; + + default: + // DQH - quiet spotbugs + break; } if (this.is(REMOVED)) { From fe441142115453f356bb029f18c4d85803596006 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 20:10:47 -0400 Subject: [PATCH 28/84] spotless --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index b899eec036e..cfc000bd6a2 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1068,10 +1068,10 @@ public final Object objectValue() { case DOUBLE: this.obj = prim2Double(this.prim); break; - + default: // DQH - quiet spotbugs - break; + break; } if (this.is(REMOVED)) { From ad8228b6361ef188eeea6143ea091cae1ba764a0 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 18 Mar 2025 08:10:15 -0400 Subject: [PATCH 29/84] Incorporating TagMap into the tracer The tracer does some Map operations regularly that regular HashMaps aren't good at. The primary operation of concern being copying Entry-s from Map to Map where every copied Entry requires allocating a new Entry object. And secondarily, Builder patterns which use defensive copying but also require in-order processing in the Tracer. TagMap solves both those problems by using immutable Entry objects. By making the Entry objects immutable, the Entry objects can be freely shared between Map instances and between the Builder and a Map. By using these Maps in key places, this change significantly reduce the cost of span construction both in terms of CPU time and memory. On an ARM 64 machine, span creation benchmarks improve by 15-45% while reducing memory consumption by 10-20%. To get the benefit of this data structure, both the source Map and the destination Map need to be TagMaps and the transfer needs to happen through putAll or the TagMap specific putEntry. Meaning - that to get a significant gain quite a few files had to be modified --- .../domain/AbstractTestSession.java | 3 +- .../trace/civisibility/domain/TestImpl.java | 3 +- .../DefaultExceptionDebuggerTest.java | 3 +- .../appsec/AppSecSystemSpecification.groovy | 2 +- .../java/datadog/trace/core/CoreTracer.java | 168 +- .../main/java/datadog/trace/core/DDSpan.java | 3 +- .../datadog/trace/core/DDSpanContext.java | 87 +- .../java/datadog/trace/core/Metadata.java | 13 +- .../trace/core/propagation/B3HttpCodec.java | 10 +- .../core/propagation/ContextInterpreter.java | 30 +- .../core/propagation/ExtractedContext.java | 22 +- .../core/taginterceptor/TagInterceptor.java | 45 + .../core/tagprocessor/BaseServiceAdder.java | 12 +- .../tagprocessor/PayloadTagsProcessor.java | 23 +- .../tagprocessor/PeerServiceCalculator.java | 20 +- .../core/tagprocessor/PostProcessorChain.java | 11 +- .../core/tagprocessor/QueryObfuscator.java | 12 +- .../tagprocessor/RemoteHostnameAdder.java | 10 +- .../tagprocessor/SpanPointersProcessor.java | 9 +- .../core/tagprocessor/TagsPostProcessor.java | 22 +- .../trace/common/writer/TraceGenerator.groovy | 3 +- .../trace/core/CoreSpanBuilderTest.groovy | 7 +- .../datadog/trace/core/DDSpanTest.groovy | 3 +- .../PostProcessorChainTest.groovy | 29 +- .../groovy/TraceGenerator.groovy | 5 +- .../main/java/datadog/trace/api/Config.java | 8 +- .../main/java/datadog/trace/api/TagMap.java | 2035 +++++++++++++++++ .../datadog/trace/api/gateway/IGSpanInfo.java | 3 +- .../trace/api/naming/NamingSchema.java | 6 +- .../api/naming/v0/PeerServiceNamingV0.java | 4 +- .../api/naming/v1/PeerServiceNamingV1.java | 16 +- .../instrumentation/api/AgentSpan.java | 6 + .../instrumentation/api/ExtractedSpan.java | 12 +- .../instrumentation/api/NoopSpan.java | 9 +- .../instrumentation/api/TagContext.java | 23 +- .../api/ExtractedSpanTest.groovy | 3 +- 36 files changed, 2477 insertions(+), 203 deletions(-) create mode 100644 internal-api/src/main/java/datadog/trace/api/TagMap.java diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java index 51d2b1ceec9..a63f2e8785e 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java @@ -6,6 +6,7 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; import datadog.trace.api.IdGenerationStrategy; +import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric; import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector; @@ -72,7 +73,7 @@ public AbstractTestSession( AgentSpanContext traceContext = new TagContext( CIConstants.CIAPP_TEST_ORIGIN, - Collections.emptyMap(), + null, null, null, PrioritySampling.UNSET, diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java index f869618117d..01c996254d3 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java @@ -6,6 +6,7 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.DDTest; import datadog.trace.api.civisibility.InstrumentationTestBridge; @@ -102,7 +103,7 @@ public TestImpl( this.context = new TestContextImpl(coverageStore); AgentSpanContext traceContext = - new TagContext(CIConstants.CIAPP_TEST_ORIGIN, Collections.emptyMap()); + new TagContext(CIConstants.CIAPP_TEST_ORIGIN, null); AgentTracer.SpanBuilder spanBuilder = AgentTracer.get() .buildSpan(CI_VISIBILITY_INSTRUMENTATION_NAME, testDecorator.component() + ".test") diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java index aaabb300cc0..bbc2e3f33c1 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java @@ -27,6 +27,7 @@ import com.datadog.debugger.util.ExceptionHelper; import com.datadog.debugger.util.TestSnapshotListener; import datadog.trace.api.Config; +import datadog.trace.api.TagMap; import datadog.trace.bootstrap.debugger.CapturedContext; import datadog.trace.bootstrap.debugger.CapturedStackFrame; import datadog.trace.bootstrap.debugger.MethodLocation; @@ -57,7 +58,7 @@ public class DefaultExceptionDebuggerTest { private ConfigurationUpdater configurationUpdater; private DefaultExceptionDebugger exceptionDebugger; private TestSnapshotListener listener; - private Map spanTags = new HashMap<>(); + private TagMap spanTags = new TagMap(); @BeforeEach public void setUp() { diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy index a92960e0a13..243c7bd4b20 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy @@ -79,7 +79,7 @@ class AppSecSystemSpecification extends DDSpecification { 1 * appSecReqCtx.transferCollectedEvents() >> [Stub(AppSecEvent)] 1 * appSecReqCtx.getRequestHeaders() >> ['foo-bar': ['1.1.1.1']] 1 * appSecReqCtx.getResponseHeaders() >> [:] - 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') + // 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') } void 'throws if the config file is not parseable'() { 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 5d033f13c78..33aa866fd79 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 @@ -30,6 +30,7 @@ import datadog.trace.api.EndpointTracker; import datadog.trace.api.IdGenerationStrategy; import datadog.trace.api.StatsDClient; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationBehaviorExtract; import datadog.trace.api.config.GeneralConfig; @@ -187,11 +188,13 @@ public static CoreTracerBuilder builder() { private final DynamicConfig dynamicConfig; /** A set of tags that are added only to the application's root span */ - private final Map localRootSpanTags; + private final TagMap localRootSpanTags; + private final boolean localRootSpanTagsNeedIntercept; /** A set of tags that are added to every span */ - private final Map defaultSpanTags; - + private final TagMap defaultSpanTags; + private boolean defaultSpanTagsNeedsIntercept; + /** number of spans in a pending trace before they get flushed */ private final int partialFlushMinSpans; @@ -309,8 +312,8 @@ public static class CoreTracerBuilder { private HttpCodec.Injector injector; private HttpCodec.Extractor extractor; private ContinuableScopeManager scopeManager; - private Map localRootSpanTags; - private Map defaultSpanTags; + private TagMap localRootSpanTags; + private TagMap defaultSpanTags; private Map serviceNameMappings; private Map taggedHeaders; private Map baggageMapping; @@ -370,12 +373,22 @@ public CoreTracerBuilder extractor(HttpCodec.Extractor extractor) { } public CoreTracerBuilder localRootSpanTags(Map localRootSpanTags) { - this.localRootSpanTags = tryMakeImmutableMap(localRootSpanTags); + this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); + return this; + } + + public CoreTracerBuilder localRootSpanTags(TagMap tagMap) { + this.localRootSpanTags = tagMap.immutableCopy(); return this; } public CoreTracerBuilder defaultSpanTags(Map defaultSpanTags) { - this.defaultSpanTags = tryMakeImmutableMap(defaultSpanTags); + this.defaultSpanTags = TagMap.fromMapImmutable(defaultSpanTags); + return this; + } + + public CoreTracerBuilder defaultSpanTags(TagMap defaultSpanTags) { + this.defaultSpanTags = defaultSpanTags.immutableCopy(); return this; } @@ -524,6 +537,63 @@ public CoreTracer build() { flushOnClose); } } + + @Deprecated + private CoreTracer( + final Config config, + final String serviceName, + SharedCommunicationObjects sharedCommunicationObjects, + final Writer writer, + final IdGenerationStrategy idGenerationStrategy, + final Sampler sampler, + final SingleSpanSampler singleSpanSampler, + final HttpCodec.Injector injector, + final HttpCodec.Extractor extractor, + final Map localRootSpanTags, + final TagMap defaultSpanTags, + final Map serviceNameMappings, + final Map taggedHeaders, + final Map baggageMapping, + final int partialFlushMinSpans, + final StatsDClient statsDClient, + final TagInterceptor tagInterceptor, + final boolean strictTraceWrites, + final InstrumentationGateway instrumentationGateway, + final TimeSource timeSource, + final DataStreamsMonitoring dataStreamsMonitoring, + final ProfilingContextIntegration profilingContextIntegration, + final boolean pollForTracerFlareRequests, + final boolean pollForTracingConfiguration, + final boolean injectBaggageAsTags, + final boolean flushOnClose) { + this( + config, + serviceName, + sharedCommunicationObjects, + writer, + idGenerationStrategy, + sampler, + singleSpanSampler, + injector, + extractor, + TagMap.fromMap(localRootSpanTags), + defaultSpanTags, + serviceNameMappings, + taggedHeaders, + baggageMapping, + partialFlushMinSpans, + statsDClient, + tagInterceptor, + strictTraceWrites, + instrumentationGateway, + timeSource, + dataStreamsMonitoring, + profilingContextIntegration, + pollForTracerFlareRequests, + pollForTracingConfiguration, + injectBaggageAsTags, + flushOnClose); + } // These field names must be stable to ensure the builder api is stable. private CoreTracer( @@ -536,8 +606,8 @@ private CoreTracer( final SingleSpanSampler singleSpanSampler, final HttpCodec.Injector injector, final HttpCodec.Extractor extractor, - final Map localRootSpanTags, - final Map defaultSpanTags, + final TagMap localRootSpanTags, + final TagMap defaultSpanTags, final Map serviceNameMappings, final Map taggedHeaders, final Map baggageMapping, @@ -590,7 +660,11 @@ private CoreTracer( spanSamplingRules = SpanSamplingRules.deserializeFile(spanSamplingRulesFile); } + this.tagInterceptor = + null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor; + this.defaultSpanTags = defaultSpanTags; + this.defaultSpanTagsNeedsIntercept = this.tagInterceptor.needsIntercept(this.defaultSpanTags); this.dynamicConfig = DynamicConfig.create(ConfigSnapshot::new) @@ -779,12 +853,14 @@ private CoreTracer( this.flushOnClose = flushOnClose; this.allowInferredServices = SpanNaming.instance().namingSchema().allowInferredServices(); if (profilingContextIntegration != ProfilingContextIntegration.NoOp.INSTANCE) { - Map tmp = new HashMap<>(localRootSpanTags); + TagMap tmp = TagMap.fromMap(localRootSpanTags); tmp.put(PROFILING_CONTEXT_ENGINE, profilingContextIntegration.name()); - this.localRootSpanTags = tryMakeImmutableMap(tmp); + this.localRootSpanTags = tmp.freeze(); } else { - this.localRootSpanTags = localRootSpanTags; + this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); } + + this.localRootSpanTagsNeedIntercept = this.tagInterceptor.needsIntercept(this.localRootSpanTags); } /** Used by AgentTestRunner to inject configuration into the test tracer. */ @@ -1290,7 +1366,7 @@ public class CoreSpanBuilder implements AgentTracer.SpanBuilder { private final CoreTracer tracer; // Builder attributes - private Map tags; + private TagMap.Builder tagBuilder; private long timestampMicro; private AgentSpanContext parent; private String serviceName; @@ -1421,14 +1497,14 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { if (tag == null) { return this; } - Map tagMap = tags; - if (tagMap == null) { - tags = tagMap = new LinkedHashMap<>(); // Insertion order is important + TagMap.Builder tagBuilder = this.tagBuilder; + if (tagBuilder == null) { + this.tagBuilder = tagBuilder = TagMap.builder(); // Insertion order is important } if (value == null) { - tagMap.remove(tag); + tagBuilder.remove(tag); } else { - tagMap.put(tag, value); + tagBuilder.put(tag, value); } return this; } @@ -1480,9 +1556,10 @@ private DDSpanContext buildSpanContext() { final TraceCollector parentTraceCollector; final int samplingPriority; final CharSequence origin; - final Map coreTags; - final Map rootSpanTags; - + final TagMap coreTags; + boolean coreTagsNeedsIntercept; + final TagMap rootSpanTags; + boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; Object requestContextDataAppSec; Object requestContextDataIast; @@ -1542,7 +1619,9 @@ private DDSpanContext buildSpanContext() { samplingPriority = PrioritySampling.UNSET; origin = null; coreTags = null; + coreTagsNeedsIntercept = false; rootSpanTags = null; + rootSpanTagsNeedsIntercept = false; parentServiceName = ddsc.getServiceName(); if (serviceName == null) { serviceName = parentServiceName; @@ -1595,6 +1674,7 @@ private DDSpanContext buildSpanContext() { TagContext tc = (TagContext) parentContext; traceConfig = (ConfigSnapshot) tc.getTraceConfig(); coreTags = tc.getTags(); + coreTagsNeedsIntercept = true; // may intercept isn't needed? origin = tc.getOrigin(); baggage = tc.getBaggage(); requestContextDataAppSec = tc.getRequestContextDataAppSec(); @@ -1603,6 +1683,7 @@ private DDSpanContext buildSpanContext() { } else { traceConfig = null; coreTags = null; + coreTagsNeedsIntercept = false; origin = null; baggage = null; requestContextDataAppSec = null; @@ -1611,6 +1692,7 @@ private DDSpanContext buildSpanContext() { } rootSpanTags = localRootSpanTags; + rootSpanTagsNeedsIntercept = localRootSpanTagsNeedIntercept; parentTraceCollector = createTraceCollector(traceId, traceConfig); @@ -1661,14 +1743,16 @@ private DDSpanContext buildSpanContext() { final CharSequence operationName = this.operationName != null ? this.operationName : resourceName; - final Map mergedTracerTags = traceConfig.mergedTracerTags; + final TagMap mergedTracerTags = traceConfig.mergedTracerTags; + boolean mergedTracerTagsNeedsIntercept = traceConfig.mergedTracerTagsNeedsIntercept; - final int tagsSize = - mergedTracerTags.size() - + (null == tags ? 0 : tags.size()) - + (null == coreTags ? 0 : coreTags.size()) - + (null == rootSpanTags ? 0 : rootSpanTags.size()) - + (null == contextualTags ? 0 : contextualTags.size()); + final int tagsSize = 0; +// final int tagsSize = +// mergedTracerTags.computeSize() +// + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) +// + (null == coreTags ? 0 : coreTags.size()) +// + (null == rootSpanTags ? 0 : rootSpanTags.size()) +// + (null == contextualTags ? 0 : contextualTags.size()); if (builderRequestContextDataAppSec != null) { requestContextDataAppSec = builderRequestContextDataAppSec; @@ -1710,13 +1794,11 @@ private DDSpanContext buildSpanContext() { // By setting the tags on the context we apply decorators to any tags that have been set via // the builder. This is the order that the tags were added previously, but maybe the `tags` // set in the builder should come last, so that they override other tags. - context.setAllTags(mergedTracerTags); - context.setAllTags(tags); - context.setAllTags(coreTags); - context.setAllTags(rootSpanTags); - if (contextualTags != null) { - context.setAllTags(contextualTags); - } + context.setAllTags(mergedTracerTags, mergedTracerTagsNeedsIntercept); + context.setAllTags(tagBuilder); + context.setAllTags(coreTags, coreTagsNeedsIntercept); + context.setAllTags(rootSpanTags, rootSpanTagsNeedsIntercept); + context.setAllTags(contextualTags); return context; } } @@ -1741,12 +1823,13 @@ public void run() { protected class ConfigSnapshot extends DynamicConfig.Snapshot { final Sampler sampler; - final Map mergedTracerTags; + final TagMap mergedTracerTags; + final boolean mergedTracerTagsNeedsIntercept; protected ConfigSnapshot( DynamicConfig.Builder builder, ConfigSnapshot oldSnapshot) { super(builder, oldSnapshot); - + if (null == oldSnapshot) { sampler = CoreTracer.this.initialSampler; } else if (Objects.equals(getTraceSampleRate(), oldSnapshot.getTraceSampleRate()) @@ -1757,11 +1840,14 @@ protected ConfigSnapshot( } if (null == oldSnapshot) { - mergedTracerTags = CoreTracer.this.defaultSpanTags; + mergedTracerTags = CoreTracer.this.defaultSpanTags.immutableCopy(); + this.mergedTracerTagsNeedsIntercept = CoreTracer.this.defaultSpanTagsNeedsIntercept; } else if (getTracingTags().equals(oldSnapshot.getTracingTags())) { mergedTracerTags = oldSnapshot.mergedTracerTags; + mergedTracerTagsNeedsIntercept = oldSnapshot.mergedTracerTagsNeedsIntercept; } else { mergedTracerTags = withTracerTags(getTracingTags(), CoreTracer.this.initialConfig, this); + mergedTracerTagsNeedsIntercept = CoreTracer.this.tagInterceptor.needsIntercept(mergedTracerTags); } } } @@ -1769,9 +1855,9 @@ protected ConfigSnapshot( /** * Tags added by the tracer to all spans; combines user-supplied tags with tracer-defined tags. */ - static Map withTracerTags( + static TagMap withTracerTags( Map userSpanTags, Config config, TraceConfig traceConfig) { - final Map result = new HashMap<>(userSpanTags.size() + 5, 1f); + final TagMap result = new TagMap(); result.putAll(userSpanTags); if (null != config) { // static if (!config.getEnv().isEmpty()) { @@ -1794,6 +1880,6 @@ protected ConfigSnapshot( result.remove(DSM_ENABLED); } } - return Collections.unmodifiableMap(result); + return result.freeze(); } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java index 771d22e3758..bac584a4151 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java @@ -12,6 +12,7 @@ import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; import datadog.trace.api.EndpointTracker; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; @@ -692,7 +693,7 @@ public String getSpanType() { } @Override - public Map getTags() { + public TagMap getTags() { // This is an imutable copy of the tags return context.getTags(); } 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 e6980e2b41d..3349205d444 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 @@ -7,6 +7,8 @@ import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; +import datadog.trace.api.TagMap; + import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; import datadog.trace.api.config.TracerConfig; @@ -94,7 +96,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 Map unsafeTags; + private final TagMap unsafeTags; /** The service name is required, otherwise the span are dropped by the agent */ private volatile String serviceName; @@ -340,10 +342,8 @@ public DDSpanContext( assert pathwayContext != null; this.pathwayContext = pathwayContext; - // 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 = new HashMap<>(capacity); + this.unsafeTags = new TagMap(); + // must set this before setting the service and resource names below this.profilingContextIntegration = profilingContextIntegration; // as fast as we can try to make this operation, we still might need to activate/deactivate @@ -749,22 +749,67 @@ public void setTag(final String tag, final Object value) { } } } - - void setAllTags(final Map map) { - if (map == null || map.isEmpty()) { + + void setAllTags(final TagMap map, boolean needsIntercept) { + if ( map == null ) { return; } - + + synchronized (unsafeTags) { + if ( needsIntercept ) { + map.forEach(this, traceCollector.getTracer().getTagInterceptor(), (ctx, tagInterceptor, tagEntry) -> { + String tag = tagEntry.tag(); + Object value = tagEntry.objectValue(); + + if (!tagInterceptor.interceptTag(ctx, tag, value)) { + ctx.unsafeTags.putEntry(tagEntry); + } + }); + } else { + unsafeTags.putAll(map); + } + } + } + + void setAllTags(final TagMap.Builder builder) { + if ( builder == null ) { + return; + } + TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { - for (final Map.Entry tag : map.entrySet()) { - if (!tagInterceptor.interceptTag(this, tag.getKey(), tag.getValue())) { - unsafeSetTag(tag.getKey(), tag.getValue()); + for (final TagMap.Entry tagEntry : builder) { + if ( tagEntry.isRemoval() ) { + unsafeTags.removeEntry(tagEntry.tag()); + } else { + String tag = tagEntry.tag(); + Object value = tagEntry.objectValue(); + + if (!tagInterceptor.interceptTag(this, tag, value)) { + unsafeTags.putEntry(tagEntry); + } } } } } + void setAllTags(final Map map) { + if ( map == null ) { + return; + } else if ( map instanceof TagMap ) { + setAllTags((TagMap)map); + } else if ( !map.isEmpty() ) { + TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); + synchronized (unsafeTags) { + for (final Map.Entry tag : map.entrySet()) { + if (!tagInterceptor.interceptTag(this, tag.getKey(), tag.getValue())) { + unsafeSetTag(tag.getKey(), tag.getValue()); + } + } + } + } + } + void unsafeSetTag(final String tag, final Object value) { unsafeTags.put(tag, value); } @@ -796,12 +841,14 @@ Object getTag(final String key) { * @return the value associated with the tag */ public Object unsafeGetTag(final String tag) { - return unsafeTags.get(tag); + return unsafeTags.getObject(tag); } - public Map getTags() { + @Deprecated + public TagMap getTags() { synchronized (unsafeTags) { - Map tags = new HashMap<>(unsafeTags); + TagMap tags = unsafeTags.copy(); + tags.put(DDTags.THREAD_ID, threadId); // maintain previously observable type of the thread name :| tags.put(DDTags.THREAD_NAME, threadName.toString()); @@ -816,7 +863,7 @@ public Map getTags() { if (value != null) { tags.put(Tags.HTTP_URL, value.toString()); } - return Collections.unmodifiableMap(tags); + return tags.freeze(); } } @@ -848,11 +895,11 @@ public void processTagsAndBaggage( final MetadataConsumer consumer, int longRunningVersion, List links) { synchronized (unsafeTags) { // Tags - Map tags = - TagsPostProcessorFactory.instance().processTags(unsafeTags, this, links); + TagsPostProcessorFactory.instance().processTags(unsafeTags, this, links); + String linksTag = DDSpanLink.toTag(links); if (linksTag != null) { - tags.put(SPAN_LINKS, linksTag); + unsafeTags.put(SPAN_LINKS, linksTag); } // Baggage Map baggageItemsWithPropagationTags; @@ -867,7 +914,7 @@ public void processTagsAndBaggage( new Metadata( threadId, threadName, - tags, + unsafeTags, baggageItemsWithPropagationTags, samplingPriority != PrioritySampling.UNSET ? samplingPriority : getSamplingPriority(), measured, 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 f1f79454164..08a1f46ca9f 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,14 +2,17 @@ import static datadog.trace.api.sampling.PrioritySampling.UNSET; -import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import java.util.HashMap; import java.util.Map; +import datadog.trace.api.TagMap; +import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; + public final class Metadata { private final long threadId; private final UTF8BytesString threadName; private final UTF8BytesString httpStatusCode; - private final Map tags; + private final TagMap tags; private final Map baggage; private final int samplingPriority; @@ -21,7 +24,7 @@ public final class Metadata { public Metadata( long threadId, UTF8BytesString threadName, - Map tags, + TagMap tags, Map baggage, int samplingPriority, boolean measured, @@ -57,8 +60,8 @@ public UTF8BytesString getThreadName() { return threadName; } - public Map getTags() { - return tags; + public TagMap getTags() { + return this.tags; } public Map getBaggage() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java index 910449b7dd1..f13cabf2ec6 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java @@ -186,10 +186,7 @@ public B3BaseContextInterpreter(Config config) { protected void setSpanId(final String sId) { spanId = DDSpanId.fromHex(sId); - if (tags.isEmpty()) { - tags = new TreeMap<>(); - } - tags.put(B3_SPAN_ID, sId); + tagBuilder().put(B3_SPAN_ID, sId); } protected boolean setTraceId(final String tId) { @@ -202,10 +199,7 @@ protected boolean setTraceId(final String tId) { B3TraceId b3TraceId = B3TraceId.fromHex(tId); traceId = b3TraceId.toLong() == 0 ? DDTraceId.ZERO : b3TraceId; } - if (tags.isEmpty()) { - tags = new TreeMap<>(); - } - tags.put(B3_TRACE_ID, tId); + tagBuilder().put(B3_TRACE_ID, tId); return true; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java index d3466f76d8b..b8e693a57f9 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java @@ -15,10 +15,15 @@ import static datadog.trace.core.propagation.HttpCodec.X_FORWARDED_PROTO_KEY; import static datadog.trace.core.propagation.HttpCodec.X_REAL_IP_KEY; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.cache.DDCache; @@ -27,9 +32,6 @@ import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.TagContext; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; /** * When adding new context fields to the ContextInterpreter class remember to clear them in the @@ -44,7 +46,7 @@ public abstract class ContextInterpreter implements AgentPropagation.KeyClassifi protected DDTraceId traceId; protected long spanId; protected int samplingPriority; - protected Map tags; + protected TagMap.Builder tagBuilder; protected Map baggage; protected CharSequence lastParentId; @@ -76,6 +78,13 @@ protected ContextInterpreter(Config config) { this.propagationTagsFactory = PropagationTags.factory(config); this.requestHeaderTagsCommaAllowed = config.isRequestHeaderTagsCommaAllowed(); } + + final TagMap.Builder tagBuilder() { + if ( tagBuilder == null ) { + tagBuilder = TagMap.builder(); + } + return tagBuilder; + } /** * Gets the propagation style handled by the context interpreter. @@ -189,10 +198,7 @@ protected final boolean handleTags(String key, String value) { final String lowerCaseKey = toLowerCase(key); final String mappedKey = headerTags.get(lowerCaseKey); if (null != mappedKey) { - if (tags.isEmpty()) { - tags = new TreeMap<>(); - } - tags.put( + tagBuilder().put( mappedKey, HttpCodec.decode( requestHeaderTagsCommaAllowed ? value : HttpCodec.firstHeaderValue(value))); @@ -224,7 +230,7 @@ public ContextInterpreter reset(TraceConfig traceConfig) { samplingPriority = PrioritySampling.UNSET; origin = null; endToEndStartTime = 0; - tags = Collections.emptyMap(); + if ( tagBuilder != null ) tagBuilder.reset(); baggage = Collections.emptyMap(); valid = true; fullContext = true; @@ -252,19 +258,19 @@ protected TagContext build() { origin, endToEndStartTime, baggage, - tags, + tagBuilder == null ? null : tagBuilder.build(), httpHeaders, propagationTags, traceConfig, style()); } else if (origin != null - || !tags.isEmpty() + || (tagBuilder != null && !tagBuilder.isDefinitelyEmpty()) || httpHeaders != null || !baggage.isEmpty() || samplingPriority != PrioritySampling.UNSET) { return new TagContext( origin, - tags, + tagBuilder == null ? null : tagBuilder.build(), httpHeaders, baggage, samplingPriorityOrDefault(traceId, samplingPriority), diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java index e799688401d..07e594adc47 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java @@ -1,6 +1,7 @@ package datadog.trace.core.propagation; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.sampling.PrioritySampling; @@ -44,7 +45,7 @@ public ExtractedContext( final CharSequence origin, final long endToEndStartTime, final Map baggage, - final Map tags, + final TagMap tags, final HttpHeaders httpHeaders, final PropagationTags propagationTags, final TraceConfig traceConfig, @@ -63,6 +64,25 @@ public ExtractedContext( this.endToEndStartTime = endToEndStartTime; this.propagationTags = propagationTags; } + + /* + * DQH - kept for testing purposes only + */ + @Deprecated + public ExtractedContext( + final DDTraceId traceId, + final long spanId, + final int samplingPriority, + final CharSequence origin, + final long endToEndStartTime, + final Map baggage, + final Map tags, + final HttpHeaders httpHeaders, + final PropagationTags propagationTags, + final TraceConfig traceConfig, + final TracePropagationStyle propagationStyle) { + this(traceId, spanId, samplingPriority, origin, endToEndStartTime, baggage, tags == null ? null : TagMap.fromMap(tags), httpHeaders, propagationTags, traceConfig, propagationStyle); + } @Override public final DDTraceId getTraceId() { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index 931eca80721..b0f10a5f3bc 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -22,6 +22,7 @@ import datadog.trace.api.ConfigDefaults; import datadog.trace.api.DDTags; import datadog.trace.api.Pair; +import datadog.trace.api.TagMap; import datadog.trace.api.config.GeneralConfig; import datadog.trace.api.env.CapturedEnvironment; import datadog.trace.api.normalize.HttpResourceNames; @@ -35,6 +36,7 @@ import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.core.DDSpanContext; import java.net.URI; +import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -81,6 +83,49 @@ public TagInterceptor( shouldSetUrlResourceAsName = ruleFlags.isEnabled(URL_AS_RESOURCE_NAME); this.jeeSplitByDeployment = jeeSplitByDeployment; } + + public boolean needsIntercept(TagMap map) { + for ( TagMap.Entry entry: map ) { + if ( needsIntercept(entry.tag()) ) return true; + } + return false; + } + + public boolean needsIntercept(Map map) { + for ( String tag: map.keySet() ) { + if ( needsIntercept(tag) ) return true; + } + return false; + } + + public boolean needsIntercept(String tag) { + switch (tag) { + case DDTags.RESOURCE_NAME: + case Tags.DB_STATEMENT: + case DDTags.SERVICE_NAME: + case "service": + case Tags.PEER_SERVICE: + case DDTags.MANUAL_KEEP: + case DDTags.MANUAL_DROP: + case Tags.ASM_KEEP: + case Tags.SAMPLING_PRIORITY: + case Tags.PROPAGATED_TRACE_SOURCE: + case Tags.PROPAGATED_DEBUG: + case InstrumentationTags.SERVLET_CONTEXT: + case SPAN_TYPE: + case ANALYTICS_SAMPLE_RATE: + case Tags.ERROR: + case HTTP_STATUS: + case HTTP_METHOD: + case HTTP_URL: + case ORIGIN_KEY: + case MEASURED: + return true; + + default: + return splitServiceTags.contains(tag); + } + } public boolean interceptTag(DDSpanContext span, String tag, Object value) { switch (tag) { 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 c855262f048..5d43edd8046 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,15 @@ 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 class BaseServiceAdder implements TagsPostProcessor { +public final class BaseServiceAdder extends TagsPostProcessor { private final UTF8BytesString ddService; public BaseServiceAdder(@Nullable final String ddService) { @@ -16,14 +17,13 @@ public BaseServiceAdder(@Nullable final String ddService) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap 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/PayloadTagsProcessor.java b/dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java index d50c8510295..3a63ae7d390 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 @@ -2,6 +2,7 @@ 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; @@ -21,7 +22,7 @@ import org.slf4j.LoggerFactory; /** Post-processor that extracts tags from payload data injected as tags by instrumentations. */ -public final class PayloadTagsProcessor implements TagsPostProcessor { +public final class PayloadTagsProcessor extends TagsPostProcessor { private static final Logger log = LoggerFactory.getLogger(PayloadTagsProcessor.class); private static final String REDACTED = "redacted"; @@ -69,21 +70,22 @@ public static PayloadTagsProcessor create(Config config) { } @Override - public Map processTags( - Map spanTags, DDSpanContext spanContext, List spanLinks) { - int spanMaxTags = maxTags + spanTags.size(); + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + int spanMaxTags = maxTags + unsafeTags.computeSize(); for (Map.Entry tagPrefixRedactionRules : redactionRulesByTagPrefix.entrySet()) { String tagPrefix = tagPrefixRedactionRules.getKey(); RedactionRules redactionRules = tagPrefixRedactionRules.getValue(); - Object tagValue = spanTags.get(tagPrefix); + Object tagValue = unsafeTags.getObject(tagPrefix); if (tagValue instanceof PayloadTagsData) { - if (spanTags.remove(tagPrefix) != null) { + if (unsafeTags.remove(tagPrefix) != null) { spanMaxTags -= 1; } + PayloadTagsData payloadTagsData = (PayloadTagsData) tagValue; PayloadTagsCollector payloadTagsCollector = - new PayloadTagsCollector(maxDepth, spanMaxTags, redactionRules, tagPrefix, spanTags); + new PayloadTagsCollector(maxDepth, spanMaxTags, redactionRules, tagPrefix, unsafeTags); collectPayloadTags(payloadTagsData, payloadTagsCollector); } else if (tagValue != null) { log.debug( @@ -93,7 +95,6 @@ public Map processTags( tagValue); } } - return spanTags; } private void collectPayloadTags( @@ -187,14 +188,14 @@ private static final class PayloadTagsCollector implements JsonStreamParser.Visi private final RedactionRules redactionRules; private final String tagPrefix; - private final Map collectedTags; + private final TagMap collectedTags; public PayloadTagsCollector( int maxDepth, int maxTags, RedactionRules redactionRules, String tagPrefix, - Map collectedTags) { + TagMap collectedTags) { this.maxDepth = maxDepth; this.maxTags = maxTags; this.redactionRules = redactionRules; @@ -259,7 +260,7 @@ public boolean keepParsing(PathCursor path) { } public boolean keepCollectingTags() { - if (collectedTags.size() < maxTags) { + if (collectedTags.computeSize() < maxTags) { return true; } collectedTags.put(DD_PAYLOAD_TAGS_INCOMPLETE, true); 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 625fae0f839..5f979ec8f04 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,16 +2,18 @@ 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; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.DDSpanContext; + import java.util.List; import java.util.Map; import javax.annotation.Nonnull; -public class PeerServiceCalculator implements TagsPostProcessor { +public final class PeerServiceCalculator extends TagsPostProcessor { private final NamingSchema.ForPeerService peerServiceNaming; private final Map peerServiceMapping; @@ -32,25 +34,26 @@ public PeerServiceCalculator() { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - Object peerService = unsafeTags.get(Tags.PEER_SERVICE); + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Object peerService = unsafeTags.getObject(Tags.PEER_SERVICE); // the user set it if (peerService != null) { if (canRemap) { - return remapPeerService(unsafeTags, peerService); + remapPeerService(unsafeTags, peerService); + return; } } else if (peerServiceNaming.supports()) { // calculate the defaults (if any) peerServiceNaming.tags(unsafeTags); // only remap if the mapping is not empty (saves one get) - return remapPeerService(unsafeTags, canRemap ? unsafeTags.get(Tags.PEER_SERVICE) : null); + remapPeerService(unsafeTags, canRemap ? unsafeTags.getObject(Tags.PEER_SERVICE) : null); + return; } // we have no peer.service and we do not compute defaults. Leave the map untouched - return unsafeTags; } - private Map remapPeerService(Map unsafeTags, Object value) { + private void remapPeerService(TagMap unsafeTags, Object value) { if (value != null) { String mapped = peerServiceMapping.get(value); if (mapped != null) { @@ -58,6 +61,5 @@ private Map remapPeerService(Map unsafeTags, Obj unsafeTags.put(DDTags.PEER_SERVICE_REMAPPED_FROM, value); } } - return unsafeTags; } } 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 fbf5b511e24..5bef7746540 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,15 @@ 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 class PostProcessorChain implements TagsPostProcessor { +public final class PostProcessorChain extends TagsPostProcessor { private final TagsPostProcessor[] chain; public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { @@ -15,12 +17,9 @@ public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - Map currentTags = unsafeTags; + public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { for (final TagsPostProcessor tagsPostProcessor : chain) { - currentTags = tagsPostProcessor.processTags(currentTags, spanContext, spanLinks); + tagsPostProcessor.processTags(unsafeTags, spanContext, spanLinks); } - return currentTags; } } 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 934aa5df1f0..19fa70cc52f 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,6 +4,7 @@ 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; @@ -13,7 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class QueryObfuscator implements TagsPostProcessor { +public final class QueryObfuscator extends TagsPostProcessor { private static final Logger log = LoggerFactory.getLogger(QueryObfuscator.class); @@ -58,20 +59,17 @@ private String obfuscate(String query) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - Object query = unsafeTags.get(DDTags.HTTP_QUERY); + public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + Object query = unsafeTags.getObject(DDTags.HTTP_QUERY); if (query instanceof CharSequence) { query = obfuscate(query.toString()); unsafeTags.put(DDTags.HTTP_QUERY, query); - Object url = unsafeTags.get(Tags.HTTP_URL); + Object url = unsafeTags.getObject(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 b872b824e38..953e8a67387 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,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.core.DDSpanContext; + import java.util.List; -import java.util.Map; import java.util.function.Supplier; -public class RemoteHostnameAdder implements TagsPostProcessor { +public final class RemoteHostnameAdder extends TagsPostProcessor { private final Supplier hostnameSupplier; public RemoteHostnameAdder(Supplier hostnameSupplier) { @@ -15,11 +16,10 @@ public RemoteHostnameAdder(Supplier hostnameSupplier) { } @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap 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 c3932b87785..abb01123e39 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,6 +11,7 @@ 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; @@ -25,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SpanPointersProcessor implements TagsPostProcessor { +public class SpanPointersProcessor extends TagsPostProcessor { private static final Logger LOG = LoggerFactory.getLogger(SpanPointersProcessor.class); // The pointer direction will always be down. The serverless agent handles cases where the @@ -36,8 +37,8 @@ public class SpanPointersProcessor implements TagsPostProcessor { public static final String LINK_KIND = "span-pointer"; @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { AgentSpanLink s3Link = handleS3SpanPointer(unsafeTags); if (s3Link != null) { spanLinks.add(s3Link); @@ -161,4 +162,4 @@ private static AgentSpanLink buildSpanPointer(String hash, String ptrKind) { return SpanLink.from(noopSpanContext(), DEFAULT_FLAGS, "", attributes); } -} +} \ No newline at end of file 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 f188e10d090..4050e9baf30 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,11 +1,27 @@ 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 interface TagsPostProcessor { - Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks); +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); } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy b/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy index db3bd899805..168fa2ce24d 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/common/writer/TraceGenerator.groovy @@ -4,6 +4,7 @@ import datadog.trace.api.DDSpanId import datadog.trace.api.DDTags import datadog.trace.api.DDTraceId import datadog.trace.api.IdGenerationStrategy +import datadog.trace.api.TagMap import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString import datadog.trace.core.CoreSpan @@ -176,7 +177,7 @@ class TraceGenerator { this.measured = measured this.samplingPriority = samplingPriority this.metadata = new Metadata(Thread.currentThread().getId(), - UTF8BytesString.create(Thread.currentThread().getName()), tags, baggage, samplingPriority, measured, topLevel, + UTF8BytesString.create(Thread.currentThread().getName()), TagMap.fromMap(tags), baggage, samplingPriority, measured, topLevel, statusCode == 0 ? null : UTF8BytesString.create(Integer.toString(statusCode)), origin, 0) this.httpStatusCode = (short) statusCode } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy index a9a86f5cdad..56445fa6be6 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/CoreSpanBuilderTest.groovy @@ -8,6 +8,7 @@ import static datadog.trace.api.DDTags.SCHEMA_VERSION_TAG_KEY import datadog.trace.api.Config import datadog.trace.api.DDSpanId import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.naming.SpanNaming import datadog.trace.api.sampling.PrioritySampling @@ -382,9 +383,9 @@ class CoreSpanBuilderTest extends DDCoreSpecification { ] + productTags() where: - tagContext | _ - new TagContext(null, [:]) | _ - new TagContext("some-origin", ["asdf": "qwer"]) | _ + tagContext | _ + new TagContext(null, TagMap.fromMap([:])) | _ + new TagContext("some-origin", TagMap.fromMap(["asdf": "qwer"])) | _ } def "global span tags populated on each span"() { diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy index a66042c50d9..54edfd095df 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/DDSpanTest.groovy @@ -3,6 +3,7 @@ package datadog.trace.core import datadog.trace.api.DDSpanId import datadog.trace.api.DDTags import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap import datadog.trace.api.gateway.RequestContextSlot import datadog.trace.api.sampling.PrioritySampling import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext @@ -274,7 +275,7 @@ class DDSpanTest extends DDCoreSpecification { where: extractedContext | _ - new TagContext("some-origin", [:]) | _ + new TagContext("some-origin", TagMap.fromMap([:])) | _ new ExtractedContext(DDTraceId.ONE, 2, PrioritySampling.SAMPLER_DROP, "some-origin", propagationTagsFactory.empty(), DATADOG) | _ } 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 2a1dc2583f3..cd82cc1a2a6 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 @@ -1,5 +1,6 @@ package datadog.trace.core.tagprocessor +import datadog.trace.api.TagMap import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink import datadog.trace.core.DDSpanContext import datadog.trace.test.util.DDSpecification @@ -9,55 +10,53 @@ class PostProcessorChainTest extends DDSpecification { setup: def processor1 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { unsafeTags.put("key1", "processor1") unsafeTags.put("key2", "processor1") - return unsafeTags } } def processor2 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { unsafeTags.put("key1", "processor2") - return unsafeTags } } def chain = new PostProcessorChain(processor1, processor2) - def tags = ["key1": "root", "key3": "root"] + def tags = TagMap.fromMap(["key1": "root", "key3": "root"]) when: - def out = chain.processTags(tags, null, []) + chain.processTags(tags, null, []) then: - assert out == ["key1": "processor2", "key2": "processor1", "key3": "root"] + assert tags == ["key1": "processor2", "key2": "processor1", "key3": "root"] } def "processor can hide tags to next one()"() { setup: def processor1 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { - return ["my": "tag"] + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + unsafeTags.clear() + unsafeTags.put("my", "tag") } } def processor2 = new TagsPostProcessor() { @Override - Map processTags(Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { if (unsafeTags.containsKey("test")) { unsafeTags.put("found", "true") - } - return unsafeTags + } } } def chain = new PostProcessorChain(processor1, processor2) - def tags = ["test": "test"] + def tags = TagMap.fromMap(["test": "test"]) when: - def out = chain.processTags(tags, null, []) + chain.processTags(tags, null, []) then: - assert out == ["my": "tag"] + assert tags == ["my": "tag"] } } diff --git a/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy b/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy index 9089f8b5a43..c796ff3f89a 100644 --- a/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy +++ b/dd-trace-core/src/traceAgentTest/groovy/TraceGenerator.groovy @@ -2,6 +2,7 @@ import datadog.trace.api.DDSpanId import datadog.trace.api.DDTags import datadog.trace.api.DDTraceId import datadog.trace.api.IdGenerationStrategy +import datadog.trace.api.TagMap import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString import datadog.trace.core.CoreSpan import datadog.trace.core.Metadata @@ -155,7 +156,7 @@ class TraceGenerator { this.type = type this.measured = measured this.metadata = new Metadata(Thread.currentThread().getId(), - UTF8BytesString.create(Thread.currentThread().getName()), tags, baggage, UNSET, measured, topLevel, null, null, 0) + UTF8BytesString.create(Thread.currentThread().getName()), TagMap.fromMap(tags), baggage, UNSET, measured, topLevel, null, null, 0) } @Override @@ -297,7 +298,7 @@ class TraceGenerator { return metadata.getBaggage() } - Map getTags() { + TagMap getTags() { return metadata.getTags() } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 6a0745c6745..a6ba7c24222 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3607,10 +3607,12 @@ public boolean isJdkSocketEnabled() { } /** @return A map of tags to be applied only to the local application root span. */ - public Map getLocalRootSpanTags() { + public TagMap getLocalRootSpanTags() { final Map runtimeTags = getRuntimeTags(); - final Map result = new HashMap<>(runtimeTags.size() + 2); + + final TagMap result = new TagMap(); result.putAll(runtimeTags); + result.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); result.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); result.put(DDTags.PROFILING_ENABLED, isProfilingEnabled() ? 1 : 0); @@ -3631,7 +3633,7 @@ public Map getLocalRootSpanTags() { result.putAll(getProcessIdTag()); - return Collections.unmodifiableMap(result); + return result.freeze(); } public WellKnownTags getWellKnownTags() { diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java new file mode 100644 index 00000000000..5b7b0b45934 --- /dev/null +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -0,0 +1,2035 @@ +package datadog.trace.api; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import datadog.trace.api.function.TriConsumer; + +/** + * A super simple hash map designed for... + * - fast copy from one map to another + * - compatibility with Builder idioms + * - building small maps as fast as possible + * - storing primitives without boxing + * - minimal memory footprint + * + * This is mainly accomplished by using immutable entry objects that can + * reference an object or a primitive. By using immutable entries, + * the entry objects can be shared between builders & maps freely. + * + * This map lacks some features of a regular java.util.Map... + * - Entry object mutation + * - size tracking - falls back to computeSize + * - manipulating Map through the entrySet() or values() + * + * Also lacks features designed for handling large maps... + * - bucket array expansion + * - adaptive collision + */ +public final class TagMap implements Map, Iterable { + public static final TagMap EMPTY = createEmpty(); + + static final TagMap createEmpty() { + return new TagMap().freeze(); + } + + public static final Builder builder() { + return new Builder(); + } + + public static final Builder builder(int size) { + return new Builder(size); + } + + public static final TagMap fromMap(Map map) { + TagMap tagMap = new TagMap(); + tagMap.putAll(map); + return tagMap; + } + + public static final TagMap fromMapImmutable(Map map) { + if ( map.isEmpty() ) { + return TagMap.EMPTY; + } else { + return fromMap(map).freeze(); + } + } + + private final Object[] buckets; + private boolean frozen; + + public TagMap() { + // needs to be a power of 2 for bucket masking calculation to work as intended + this.buckets = new Object[1 << 4]; + this.frozen = false; + } + + /** + * Used for inexpensive immutable + */ + private TagMap(Object[] buckets) { + this.buckets = buckets; + this.frozen = true; + } + + @Deprecated + @Override + public final int size() { + return this.computeSize(); + } + + /** + * Computes the size of the TagMap + * + * computeSize is fast but is an O(n) operation. + */ + public final int computeSize() { + Object[] thisBuckets = this.buckets; + + int size = 0; + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object curBucket = thisBuckets[i]; + + if ( curBucket instanceof Entry ) { + size += 1; + } else if ( curBucket instanceof BucketGroup ) { + BucketGroup curGroup = (BucketGroup)curBucket; + size += curGroup.sizeInChain(); + } + } + return size; + } + + @Deprecated + @Override + public boolean isEmpty() { + return this.checkIfEmpty(); + } + + public final boolean checkIfEmpty() { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object curBucket = thisBuckets[i]; + + if ( curBucket instanceof Entry ) { + return false; + } else if ( curBucket instanceof BucketGroup ) { + BucketGroup curGroup = (BucketGroup)curBucket; + if ( !curGroup.isEmptyChain() ) return false; + } + } + + return true; + } + + @Override + public boolean containsKey(Object key) { + if ( !(key instanceof String) ) return false; + + return (this.getEntry((String)key) != null); + } + + @Override + public boolean containsValue(Object value) { + for ( Entry entry : this ) { + if ( entry.objectValue().equals(value) ) return true; + } + return false; + } + + @Deprecated + @Override + public Set keySet() { + return new Keys(this); + } + + @Deprecated + @Override + public Collection values() { + return new Values(this); + } + + @Deprecated + @Override + public Set> entrySet() { + return new Entries(this); + } + + @Deprecated + @Override + public final Object get(Object tag) { + if ( !(tag instanceof String) ) return null; + + return this.getObject((String)tag); + } + + public final Object getObject(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? null : entry.objectValue(); + } + + public final String getString(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? null : entry.stringValue(); + } + + public final boolean getBoolean(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? false : entry.booleanValue(); + } + + public final int getInt(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0 : entry.intValue(); + } + + public final long getLong(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0L : entry.longValue(); + } + + public final float getFloat(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0F : entry.floatValue(); + } + + public final double getDouble(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0D : entry.doubleValue(); + } + + public final Entry getEntry(String tag) { + Object[] thisBuckets = this.buckets; + + int hash = _hash(tag); + int bucketIndex = hash & (thisBuckets.length - 1); + + Object bucket = thisBuckets[bucketIndex]; + if ( bucket == null ) { + return null; + } else if ( bucket instanceof Entry ) { + Entry tagEntry = (Entry)bucket; + if ( tagEntry.matches(tag) ) return tagEntry; + } else if ( bucket instanceof BucketGroup ) { + BucketGroup lastGroup = (BucketGroup)bucket; + + Entry tagEntry = lastGroup.findInChain(hash, tag); + if ( tagEntry != null ) return tagEntry; + } + return null; + } + + @Deprecated + public final Object put(String tag, Object value) { + TagMap.Entry entry = this.putEntry(Entry.newAnyEntry(tag, value)); + return entry == null ? null : entry.objectValue(); + } + + public final Entry set(String tag, Object value) { + return this.putEntry(Entry.newAnyEntry(tag, value)); + } + + public final Entry set(String tag, CharSequence value) { + return this.putEntry(Entry.newObjectEntry(tag, value)); + } + + public final Entry set(String tag, boolean value) { + return this.putEntry(Entry.newBooleanEntry(tag, value)); + } + + public final Entry set(String tag, int value) { + return this.putEntry(Entry.newIntEntry(tag, value)); + } + + public final Entry set(String tag, long value) { + return this.putEntry(Entry.newLongEntry(tag, value)); + } + + public final Entry set(String tag, float value) { + return this.putEntry(Entry.newFloatEntry(tag, value)); + } + + public final Entry set(String tag, double value) { + return this.putEntry(Entry.newDoubleEntry(tag, value)); + } + + public final Entry putEntry(Entry newEntry) { + this.checkWriteAccess(); + + Object[] thisBuckets = this.buckets; + + int newHash = newEntry.hash(); + int bucketIndex = newHash & (thisBuckets.length - 1); + + Object bucket = thisBuckets[bucketIndex]; + if ( bucket == null ) { + thisBuckets[bucketIndex] = newEntry; + + return null; + } else if ( bucket instanceof Entry ) { + Entry existingEntry = (Entry)bucket; + if ( existingEntry.matches(newEntry.tag) ) { + thisBuckets[bucketIndex] = newEntry; + + return existingEntry; + } else { + thisBuckets[bucketIndex] = new BucketGroup( + existingEntry.hash(), existingEntry, + newHash, newEntry); + + return null; + } + } else if ( bucket instanceof BucketGroup ){ + BucketGroup lastGroup = (BucketGroup)bucket; + + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag); + if ( containingGroup != null ) { + return containingGroup._replace(newHash, newEntry); + } + + if ( !lastGroup.insertInChain(newHash, newEntry) ) { + thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup); + } + return null; + } + return null; + } + + public final void putAll(Iterable entries) { + this.checkWriteAccess(); + + for ( Entry tagEntry: entries ) { + if ( tagEntry.isRemoval() ) { + this.remove(tagEntry.tag); + } else { + this.putEntry(tagEntry); + } + } + } + + public final void putAll(TagMap.Builder builder) { + putAll(builder.entries, builder.nextPos); + } + + private final void putAll(Entry[] tagEntries, int size) { + for ( int i = 0; i < size && i < tagEntries.length; ++i ) { + Entry tagEntry = tagEntries[i]; + + if ( tagEntry.isRemoval() ) { + this.remove(tagEntry.tag); + } else { + this.putEntry(tagEntry); + } + } + } + + public final void putAll(TagMap that) { + this.checkWriteAccess(); + + Object[] thisBuckets = this.buckets; + Object[] thatBuckets = that.buckets; + + // Since TagMap-s don't support expansion, buckets are perfectly aligned + for ( int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i ) { + Object thatBucket = thatBuckets[i]; + + // nothing in the other hash, just skip this bucket + if ( thatBucket == null ) continue; + + Object thisBucket = thisBuckets[i]; + + if ( thisBucket == null ) { + // This bucket is null, easy case + // Either copy over the sole entry or clone the BucketGroup chain + + if ( thatBucket instanceof Entry ) { + thisBuckets[i] = thatBucket; + } else if ( thatBucket instanceof BucketGroup ) { + BucketGroup thatGroup = (BucketGroup)thatBucket; + + thisBuckets[i] = thatGroup.cloneChain(); + } + } else if ( thisBucket instanceof Entry ) { + // This bucket is a single entry, medium complexity case + // If other side is an Entry - just merge the entries into a bucket + // If other side is a BucketGroup - then clone the group and insert the entry normally into the cloned group + + Entry thisEntry = (Entry)thisBucket; + int thisHash = thisEntry.hash(); + + if ( thatBucket instanceof Entry ) { + Entry thatEntry = (Entry)thatBucket; + int thatHash = thatEntry.hash(); + + if ( thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { + thisBuckets[i] = thatEntry; + } else { + thisBuckets[i] = new BucketGroup( + thisHash, thisEntry, + thatHash, thatEntry); + } + } else if ( thatBucket instanceof BucketGroup ) { + BucketGroup thatGroup = (BucketGroup)thatBucket; + + // Clone the other group, then place this entry into that group + BucketGroup thisNewGroup = thatGroup.cloneChain(); + + Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag()); + if ( incomingEntry != null ) { + // there's already an entry w/ the same tag from the incoming TagMap - done + thisBuckets[i] = thisNewGroup; + } else if ( thisNewGroup.insertInChain(thisHash, thisEntry) ) { + // able to add thisEntry into the existing groups + thisBuckets[i] = thisNewGroup; + } else { + // unable to add into the existing groups + thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); + } + } + } else if ( thisBucket instanceof BucketGroup ) { + // This bucket is a BucketGroup, medium to hard case + // If the other side is an entry, just normal insertion procedure - no cloning required + BucketGroup thisGroup = (BucketGroup)thisBucket; + + if ( thatBucket instanceof Entry ) { + Entry thatEntry = (Entry)thatBucket; + int thatHash = thatEntry.hash(); + + if ( !thisGroup.replaceOrInsertInChain(thatHash, thatEntry) ) { + thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup); + } + } else if ( thatBucket instanceof BucketGroup ) { + // Most complicated case - need to walk that bucket group chain and update this chain + BucketGroup thatGroup = (BucketGroup)thatBucket; + + thisBuckets[i] = thisGroup.replaceOrInsertAllInChain(thatGroup); + } + } + } + } + + public final void putAll(Map map) { + this.checkWriteAccess(); + + if ( map instanceof TagMap ) { + this.putAll((TagMap)map); + } else { + for ( Map.Entry entry: map.entrySet() ) { + this.put(entry.getKey(), entry.getValue()); + } + } + } + + public final void fillMap(Map map) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + map.put(thisEntry.tag, thisEntry.objectValue()); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.fillMapFromChain(map); + } + } + } + + public final void fillStringMap(Map stringMap) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + stringMap.put(thisEntry.tag, thisEntry.stringValue()); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.fillStringMapFromChain(stringMap); + } + } + } + + @Deprecated + @Override + public final Object remove(Object tag) { + if ( !(tag instanceof String) ) return null; + + Entry entry = this.removeEntry((String)tag); + return entry == null ? null : entry.objectValue(); + } + + public final Entry removeEntry(String tag) { + this.checkWriteAccess(); + + Object[] thisBuckets = this.buckets; + + int hash = _hash(tag); + int bucketIndex = hash & (thisBuckets.length - 1); + + Object bucket = thisBuckets[bucketIndex]; + // null bucket case - do nothing + if ( bucket instanceof Entry ) { + Entry existingEntry = (Entry)bucket; + if (existingEntry.matches(tag)) { + thisBuckets[bucketIndex] = null; + return existingEntry; + } else { + return null; + } + } else if ( bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup)bucket; + + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag); + if ( containingGroup == null ) { + return null; + } + + Entry existingEntry = containingGroup._remove(hash, tag); + if ( containingGroup._isEmpty() ) { + this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup); + } + + return existingEntry; + } + return null; + } + + public final TagMap copy() { + TagMap copy = new TagMap(); + copy.putAll(this); + return copy; + } + + public final TagMap immutableCopy() { + if ( this.frozen ) { + return this; + } else { + return this.copy().freeze(); + } + } + + public final TagMap immutable() { + // specialized constructor, freezes map immediately + return new TagMap(this.buckets); + } + + @Override + public final Iterator iterator() { + return new EntryIterator(this); + } + + public final Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + public final void forEach(Consumer consumer) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + consumer.accept(thisEntry); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.forEachInChain(consumer); + } + } + } + + public final void forEach(T thisObj, BiConsumer consumer) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + consumer.accept(thisObj, thisEntry); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.forEachInChain(thisObj, consumer); + } + } + } + + public final void forEach(T thisObj, U otherObj, TriConsumer consumer) { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + + consumer.accept(thisObj, otherObj, thisEntry); + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + thisGroup.forEachInChain(thisObj, otherObj, consumer); + } + } + } + + public final void clear() { + this.checkWriteAccess(); + + Arrays.fill(this.buckets, null); + } + + public final TagMap freeze() { + this.frozen = true; + + return this; + } + + public boolean isFrozen() { + return this.frozen; + } + +// final void check() { +// Object[] thisBuckets = this.buckets; +// +// for ( int i = 0; i < thisBuckets.length; ++i ) { +// Object thisBucket = thisBuckets[i]; +// +// if ( thisBucket instanceof Entry ) { +// Entry thisEntry = (Entry)thisBucket; +// int thisHash = thisEntry.hash(); +// +// int expectedBucket = thisHash & (thisBuckets.length - 1); +// assert expectedBucket == i; +// } else if ( thisBucket instanceof BucketGroup ) { +// BucketGroup thisGroup = (BucketGroup)thisBucket; +// +// for ( BucketGroup curGroup = thisGroup; +// curGroup != null; +// curGroup = curGroup.prev ) +// { +// for ( int j = 0; j < BucketGroup.LEN; ++j ) { +// Entry thisEntry = curGroup._entryAt(i); +// if ( thisEntry == null ) continue; +// +// int thisHash = thisEntry.hash(); +// assert curGroup._hashAt(i) == thisHash; +// +// int expectedBucket = thisHash & (thisBuckets.length - 1); +// assert expectedBucket == i; +// } +// } +// } +// } +// } + + @Override + public String toString() { + return toInternalString(); + } + + String toPrettyString() { + boolean first = true; + + StringBuilder builder = new StringBuilder(128); + builder.append('{'); + for ( Entry entry: this ) { + if ( first ) { + first = false; + } else { + builder.append(","); + } + + builder.append(entry.tag).append('=').append(entry.stringValue()); + } + builder.append('}'); + return builder.toString(); + } + + String toInternalString() { + Object[] thisBuckets = this.buckets; + + StringBuilder builder = new StringBuilder(128); + for ( int i = 0; i < thisBuckets.length; ++i ) { + builder.append('[').append(i).append("] = "); + + Object thisBucket = thisBuckets[i]; + if ( thisBucket == null ) { + builder.append("null"); + } else if ( thisBucket instanceof Entry ) { + builder.append('{').append(thisBucket).append('}'); + } else if ( thisBucket instanceof BucketGroup ) { + for ( BucketGroup curGroup = (BucketGroup)thisBucket; + curGroup != null; + curGroup = curGroup.prev ) + { + builder.append(curGroup).append(" -> "); + } + } + builder.append('\n'); + } + return builder.toString(); + } + + public final void checkWriteAccess() { + if ( this.frozen ) throw new IllegalStateException("TagMap frozen"); + } + + static final int _hash(String tag) { + int hash = tag.hashCode(); + return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); + } + + public static final class Entry implements Map.Entry { + /* + * Special value used to record removals in the builder + * Removal entries are never stored in the TagMap itself + */ + private static final byte REMOVED = -1; + + /* + * Special value used for Objects that haven't been type checked yet. + * These objects might be primitive box objects. + */ + public static final byte ANY = 0; + public static final byte OBJECT = 1; + + /* + * Non-numeric primitive types + */ + public static final byte BOOLEAN = 2; + public static final byte CHAR = 3; + + /* + * Numeric constants - deliberately arranged to allow for checking by using type >= BYTE + */ + public static final byte BYTE = 4; + public static final byte SHORT = 5; + public static final byte INT = 6; + public static final byte LONG = 7; + public static final byte FLOAT = 8; + public static final byte DOUBLE = 9; + + + static final Entry newAnyEntry(String tag, Object value) { + // DQH - To keep entry creation (e.g. map changes) as fast as possible, + // the entry construction is kept as simple as possible. + + // Prior versions of this code did type detection on value to + // recognize box types but that proved expensive. So now, + // the type is recorded as an ANY which is an indicator to do + // type detection later if need be. + return new Entry(tag, ANY, 0L, value); + } + + static final Entry newObjectEntry(String tag, Object value) { + return new Entry(tag, OBJECT, 0, value); + } + + static final Entry newBooleanEntry(String tag, boolean value) { + return new Entry(tag, BOOLEAN, boolean2Prim(value), Boolean.valueOf(value)); + } + + static final Entry newBooleanEntry(String tag, Boolean box) { + return new Entry(tag, BOOLEAN, boolean2Prim(box.booleanValue()), box); + } + + static final Entry newIntEntry(String tag, int value) { + return new Entry(tag, INT, int2Prim(value), null); + } + + static final Entry newIntEntry(String tag, Integer box) { + return new Entry(tag, INT, int2Prim(box.intValue()), box); + } + + static final Entry newLongEntry(String tag, long value) { + return new Entry(tag, LONG, long2Prim(value), null); + } + + static final Entry newLongEntry(String tag, Long box) { + return new Entry(tag, LONG, long2Prim(box.longValue()), box); + } + + static final Entry newFloatEntry(String tag, float value) { + return new Entry(tag, FLOAT, float2Prim(value), null); + } + + static final Entry newFloatEntry(String tag, Float box) { + return new Entry(tag, FLOAT, float2Prim(box.floatValue()), box); + } + + static final Entry newDoubleEntry(String tag, double value) { + return new Entry(tag, DOUBLE, double2Prim(value), null); + } + + static final Entry newDoubleEntry(String tag, Double box) { + return new Entry(tag, DOUBLE, double2Prim(box.doubleValue()), box); + } + + static final Entry newRemovalEntry(String tag) { + return new Entry(tag, REMOVED, 0, null); + } + + final String tag; + int hash; + + // To optimize construction of Entry around boxed primitives and Object entries, + // no type checks are done during construction. + // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into obj + // If an ANY entry is later type checked or request as a primitive, then the ANY will be resolved + // to the correct type. + + // From the outside perspective, this object remains functionally immutable. + // However, internally, it is important to remember that this type must be thread safe. + // That includes multiple threads racing to resolve an ANY entry at the same time. + + volatile byte type; + volatile long prim; + volatile Object obj; + + volatile String strCache = null; + + private Entry(String tag, byte type, long prim, Object obj) { + this.tag = tag; + this.hash = 0; // lazily computed + this.type = type; + this.prim = prim; + this.obj = obj; + } + + public final String tag() { + return this.tag; + } + + int hash() { + int hash = this.hash; + if ( hash != 0 ) return hash; + + hash = _hash(this.tag); + this.hash = hash; + return hash; + } + + public final byte type() { + return this.resolveAny(); + } + + public final boolean is(byte type) { + byte curType = this.type; + if ( curType == type ) { + return true; + } else if ( curType != ANY ) { + return false; + } else { + return (this.resolveAny() == type); + } + } + + public final boolean isNumericPrimitive() { + byte curType = this.type; + if ( _isNumericPrimitive(curType) ) { + return true; + } else if ( curType != ANY ) { + return false; + } else { + return _isNumericPrimitive(this.resolveAny()); + } + } + + public final boolean isNumber() { + byte curType = this.type; + return _isNumericPrimitive(curType) || (this.obj instanceof Number); + } + + private static final boolean _isNumericPrimitive(byte type) { + return (type >= BYTE); + } + + private final byte resolveAny() { + byte curType = this.type; + if ( curType != ANY ) return curType; + + Object value = this.obj; + long prim; + byte resolvedType; + + if (value instanceof Boolean) { + Boolean boolValue = (Boolean) value; + prim = boolean2Prim(boolValue); + resolvedType = BOOLEAN; + } else if (value instanceof Integer) { + Integer intValue = (Integer) value; + prim = int2Prim(intValue); + resolvedType = INT; + } else if (value instanceof Long) { + Long longValue = (Long) value; + prim = long2Prim(longValue); + resolvedType = LONG; + } else if (value instanceof Float) { + Float floatValue = (Float) value; + prim = float2Prim(floatValue); + resolvedType = FLOAT; + } else if (value instanceof Double) { + Double doubleValue = (Double) value; + prim = double2Prim(doubleValue); + resolvedType = DOUBLE; + } else { + prim = 0; + resolvedType = OBJECT; + } + + this._setPrim(resolvedType, prim); + + return resolvedType; + } + + private void _setPrim(byte type, long prim) { + // Order is important here, the contract is that prim must be set properly *before* + // type is set to a non-object type + + this.prim = prim; + this.type = type; + } + + public final boolean isObject() { + return this.is(OBJECT); + } + + public final boolean isRemoval() { + return this.is(REMOVED); + } + + public final boolean matches(String tag) { + return this.tag.equals(tag); + } + + public final Object objectValue() { + if ( this.obj != null ) { + return this.obj; + } + + // This code doesn't need to handle ANY-s. + // An entry that starts as an ANY will always have this.obj set + switch (this.type) { + case BOOLEAN: + this.obj = prim2Boolean(this.prim); + break; + + case INT: + // Maybe use a wider cache that handles response code??? + this.obj = prim2Int(this.prim); + break; + + case LONG: + this.obj = prim2Long(this.prim); + break; + + case FLOAT: + this.obj = prim2Float(this.prim); + break; + + case DOUBLE: + this.obj = prim2Double(this.prim); + break; + } + + if (this.is(REMOVED)) { + return null; + } + + return this.obj; + } + + public final boolean booleanValue() { + byte type = this.type; + + if (type == BOOLEAN) { + return prim2Boolean(this.prim); + } else if (type == ANY && this.obj instanceof Boolean) { + boolean boolValue = (Boolean)this.obj; + this._setPrim(BOOLEAN, boolean2Prim(boolValue)); + return boolValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case INT: + return prim2Int(prim) != 0; + + case LONG: + return prim2Long(prim) != 0L; + + case FLOAT: + return prim2Float(prim) != 0F; + + case DOUBLE: + return prim2Double(prim) != 0D; + + case OBJECT: + return (this.obj != null); + + case REMOVED: + return false; + } + + return false; + } + + public final int intValue() { + byte type = this.type; + + if (type == INT) { + return prim2Int(this.prim); + } else if (type == ANY && this.obj instanceof Integer) { + int intValue = (Integer)this.obj; + this._setPrim(INT, int2Prim(intValue)); + return intValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1 : 0; + + case LONG: + return (int) prim2Long(prim); + + case FLOAT: + return (int) prim2Float(prim); + + case DOUBLE: + return (int) prim2Double(prim); + + case OBJECT: + return 0; + + case REMOVED: + return 0; + } + + return 0; + } + + public final long longValue() { + byte type = this.type; + + if (type == LONG) { + return prim2Long(this.prim); + } else if (type == ANY && this.obj instanceof Long) { + long longValue = (Long)this.obj; + this._setPrim(LONG, long2Prim(longValue)); + return longValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1L : 0L; + + case INT: + return (long) prim2Int(prim); + + case FLOAT: + return (long) prim2Float(prim); + + case DOUBLE: + return (long) prim2Double(prim); + + case OBJECT: + return 0; + + case REMOVED: + return 0; + } + + return 0; + } + + public final float floatValue() { + byte type = this.type; + + if (type == FLOAT) { + return prim2Float(this.prim); + } else if (type == ANY && this.obj instanceof Float) { + float floatValue = (Float)this.obj; + this._setPrim(FLOAT, float2Prim(floatValue)); + return floatValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1F : 0F; + + case INT: + return (float) prim2Int(prim); + + case LONG: + return (float) prim2Long(prim); + + case DOUBLE: + return (float) prim2Double(prim); + + case OBJECT: + return 0F; + + case REMOVED: + return 0F; + } + + return 0F; + } + + public final double doubleValue() { + byte type = this.type; + + if (type == DOUBLE) { + return prim2Double(this.prim); + } else if (type == ANY && this.obj instanceof Double) { + double doubleValue = (Double)this.obj; + this._setPrim(DOUBLE, double2Prim(doubleValue)); + return doubleValue; + } + + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.prim; + + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1D : 0D; + + case INT: + return (double) prim2Int(prim); + + case LONG: + return (double) prim2Long(prim); + + case FLOAT: + return (double) prim2Float(prim); + + case OBJECT: + return 0D; + + case REMOVED: + return 0D; + } + + return 0D; + } + + public final String stringValue() { + String strCache = this.strCache; + if ( strCache != null ) { + return strCache; + } + + String computeStr = this.computeStringValue(); + this.strCache = computeStr; + return computeStr; + } + + private final String computeStringValue() { + // Could do type resolution here, + // but decided to just fallback to this.obj.toString() for ANY case + switch (this.type) { + case BOOLEAN: + return Boolean.toString(prim2Boolean(this.prim)); + + case INT: + return Integer.toString(prim2Int(this.prim)); + + case LONG: + return Long.toString(prim2Long(this.prim)); + + case FLOAT: + return Float.toString(prim2Float(this.prim)); + + case DOUBLE: + return Double.toString(prim2Double(this.prim)); + + case REMOVED: + return null; + + case OBJECT: + case ANY: + return this.obj.toString(); + } + + return null; + } + + @Override + public final String toString() { + return this.tag() + '=' + this.stringValue(); + } + + @Deprecated + @Override + public String getKey() { + return this.tag(); + } + + @Deprecated + @Override + public Object getValue() { + return this.objectValue(); + } + + @Deprecated + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException(); + } + + private static final long boolean2Prim(boolean value) { + return value ? 1L : 0L; + } + + private static final boolean prim2Boolean(long prim) { + return (prim != 0L); + } + + private static final long int2Prim(int value) { + return (long) value; + } + + private static final int prim2Int(long prim) { + return (int) prim; + } + + private static final long long2Prim(long value) { + return value; + } + + private static final long prim2Long(long prim) { + return prim; + } + + private static final long float2Prim(float value) { + return (long) Float.floatToIntBits(value); + } + + private static final float prim2Float(long prim) { + return Float.intBitsToFloat((int) prim); + } + + private static final long double2Prim(double value) { + return Double.doubleToRawLongBits(value); + } + + private static final double prim2Double(long prim) { + return Double.longBitsToDouble(prim); + } + } + + public static final class Builder implements Iterable { + private Entry[] entries; + private int nextPos = 0; + + private Builder() { + this(8); + } + + private Builder(int size) { + this.entries = new Entry[size]; + } + + public final boolean isDefinitelyEmpty() { + return (this.nextPos == 0); + } + + /** + * Provides the estimated size of the map created by the builder + * Doesn't account for overwritten entries or entry removal + * @return + */ + public final int estimateSize() { + return this.nextPos; + } + + public final Builder put(String tag, Object value) { + return this.put(Entry.newAnyEntry(tag, value)); + } + + public final Builder put(String tag, CharSequence value) { + return this.put(Entry.newObjectEntry(tag, value)); + } + + public final Builder put(String tag, boolean value) { + return this.put(Entry.newBooleanEntry(tag, value)); + } + + public final Builder put(String tag, int value) { + return this.put(Entry.newIntEntry(tag, value)); + } + + public final Builder put(String tag, long value) { + return this.put(Entry.newLongEntry(tag, value)); + } + + public final Builder put(String tag, float value) { + return this.put(Entry.newFloatEntry(tag, value)); + } + + public final Builder put(String tag, double value) { + return this.put(Entry.newDoubleEntry(tag, value)); + } + + public final Builder remove(String tag) { + return this.put(Entry.newRemovalEntry(tag)); + } + + public final Builder put(Entry entry) { + if ( this.nextPos >= this.entries.length ) { + this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); + } + + this.entries[this.nextPos++] = entry; + return this; + } + + public final void reset() { + Arrays.fill(this.entries, null); + this.nextPos = 0; + } + + @Override + public final Iterator iterator() { + return new BuilderIterator(this.entries, this.nextPos); + } + + public TagMap build() { + TagMap map = new TagMap(); + if ( this.nextPos != 0 ) map.putAll(this.entries, this.nextPos); + return map; + } + + public TagMap buildImmutable() { + if ( this.nextPos == 0 ) { + return TagMap.EMPTY; + } else { + return this.build().freeze(); + } + } + } + + private static final class BuilderIterator implements Iterator { + private final Entry[] entries; + private final int size; + + private int pos; + + BuilderIterator(Entry[] entries, int size) { + this.entries = entries; + this.size = size; + + this.pos = -1; + } + + @Override + public final boolean hasNext() { + return ( this.pos + 1 < this.size ); + } + + @Override + public Entry next() { + if ( !this.hasNext() ) throw new NoSuchElementException("no next"); + + return this.entries[++this.pos]; + } + } + + private static abstract class MapIterator implements Iterator { + private final Object[] buckets; + + private Entry nextEntry; + + private int bucketIndex = -1; + + private BucketGroup group = null; + private int groupIndex = 0; + + MapIterator(TagMap map) { + this.buckets = map.buckets; + } + + @Override + public boolean hasNext() { + if ( this.nextEntry != null ) return true; + + while ( this.bucketIndex < this.buckets.length ) { + this.nextEntry = this.advance(); + if ( this.nextEntry != null ) return true; + } + + return false; + } + + Entry nextEntry() { + if ( this.nextEntry != null ) { + Entry nextEntry = this.nextEntry; + this.nextEntry = null; + return nextEntry; + } + + if ( this.hasNext() ) { + return this.nextEntry; + } else { + throw new NoSuchElementException(); + } + } + + private final Entry advance() { + while ( this.bucketIndex < this.buckets.length ) { + if ( this.group != null ) { + for ( ++this.groupIndex; this.groupIndex < BucketGroup.LEN; ++this.groupIndex ) { + Entry tagEntry = this.group._entryAt(this.groupIndex); + if ( tagEntry != null ) return tagEntry; + } + + // done processing - that group, go to next group + this.group = this.group.prev; + this.groupIndex = -1; + } + + // if the group is null, then we've finished the current bucket - so advance the bucket + if ( this.group == null ) { + for ( ++this.bucketIndex; this.bucketIndex < this.buckets.length; ++this.bucketIndex ) { + Object bucket = this.buckets[this.bucketIndex]; + + if ( bucket instanceof Entry ) { + return (Entry)bucket; + } else if ( bucket instanceof BucketGroup ) { + this.group = (BucketGroup)bucket; + this.groupIndex = -1; + + break; + } + } + } + }; + + return null; + } + } + + static final class EntryIterator extends MapIterator { + EntryIterator(TagMap map) { + super(map); + } + + @Override + public Entry next() { + return this.nextEntry(); + } + } + + /** + * BucketGroup is compromise for performance over a linked list or array + * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities + * - array - wouldn't be able to store hashes close together + * - parallel arrays (one for hashes & another for entries) would require more allocation + */ + static final class BucketGroup { + static final int LEN = 4; + + // int hashFilter = 0; + + // want the hashes together in the same cache line + int hash0 = 0; + int hash1 = 0; + int hash2 = 0; + int hash3 = 0; + + Entry entry0 = null; + Entry entry1 = null; + Entry entry2 = null; + Entry entry3 = null; + + BucketGroup prev = null; + + BucketGroup() {} + + /** + * New group with an entry pointing to existing BucketGroup + */ + BucketGroup(int hash0, Entry entry0, BucketGroup prev) { + this.hash0 = hash0; + this.entry0 = entry0; + + this.prev = prev; + + // this.hashFilter = hash0; + } + + /** + * New group composed of two entries + */ + BucketGroup(int hash0, Entry entry0, int hash1, Entry entry1) { + this.hash0 = hash0; + this.entry0 = entry0; + + this.hash1 = hash1; + this.entry1 = entry1; + + // this.hashFilter = hash0 | hash1; + } + + /** + * New group composed of 4 entries - used for cloning + */ + BucketGroup( + int hash0, Entry entry0, + int hash1, Entry entry1, + int hash2, Entry entry2, + int hash3, Entry entry3) + { + this.hash0 = hash0; + this.entry0 = entry0; + + this.hash1 = hash1; + this.entry1 = entry1; + + this.hash2 = hash2; + this.entry2 = entry2; + + this.hash3 = hash3; + this.entry3 = entry3; + + // this.hashFilter = hash0 | hash1 | hash2 | hash3; + } + + Entry _entryAt(int index) { + switch ( index ) { + case 0: + return this.entry0; + + case 1: + return this.entry1; + + case 2: + return this.entry2; + + case 3: + return this.entry3; + } + + return null; + } + + int _hashAt(int index) { + switch ( index ) { + case 0: + return this.hash0; + + case 1: + return this.hash1; + + case 2: + return this.hash2; + + case 3: + return this.hash3; + } + + return 0; + } + + int sizeInChain() { + int size = 0; + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + size += curGroup._size(); + } + return size; + } + + int _size() { + return ( this.hash0 == 0 ? 0 : 1 ) + + ( this.hash1 == 0 ? 0 : 1 ) + + ( this.hash2 == 0 ? 0 : 1 ) + + ( this.hash3 == 0 ? 0 : 1 ); + } + + boolean isEmptyChain() { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + if ( !curGroup._isEmpty() ) return false; + } + return true; + } + + boolean _isEmpty() { + return (this.hash0 | this.hash1 | this.hash2 | this.hash3) != 0; + // return (this.hashFilter == 0); + } + + BucketGroup findContainingGroupInChain(int hash, String tag) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + if ( curGroup._find(hash, tag) != null ) return curGroup; + } + return null; + } + +// boolean _mayContain(int hash) { +// return ((hash & this.hashFilter) == hash); +// } + + Entry findInChain(int hash, String tag) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + Entry curEntry = curGroup._find(hash, tag); + if ( curEntry != null ) return curEntry; + } + return null; + } + + Entry _find(int hash, String tag) { + // if ( this._mayContain(hash) ) return null; + + if ( this.hash0 == hash && this.entry0.matches(tag)) { + return this.entry0; + } else if ( this.hash1 == hash && this.entry1.matches(tag)) { + return this.entry1; + } else if ( this.hash2 == hash && this.entry2.matches(tag)) { + return this.entry2; + } else if ( this.hash3 == hash && this.entry3.matches(tag)) { + return this.entry3; + } + return null; + } + + BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) { + BucketGroup thisOrigHeadGroup = this; + BucketGroup thisNewestHeadGroup = thisOrigHeadGroup; + + for ( BucketGroup thatCurGroup = thatHeadGroup; + thatCurGroup != null; + thatCurGroup = thatCurGroup.prev ) + { + // First phase - tries to replace or insert each entry in the existing bucket chain + // Only need to search the original groups for replacements + // The whole chain is eligible for insertions + boolean handled0 = (thatCurGroup.hash0 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash0, thatCurGroup.entry0) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash0, thatCurGroup.entry0); + + boolean handled1 = (thatCurGroup.hash1 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash1, thatCurGroup.entry1) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash1, thatCurGroup.entry1); + + boolean handled2 = (thatCurGroup.hash2 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash2, thatCurGroup.entry2) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash2, thatCurGroup.entry2); + + boolean handled3 = (thatCurGroup.hash3 == 0) || + (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash3, thatCurGroup.entry3) != null) || + thisNewestHeadGroup.insertInChain(thatCurGroup.hash3, thatCurGroup.entry3); + + // Second phase - takes any entries that weren't handled by phase 1 and puts them + // into a new BucketGroup. Since BucketGroups are fixed size, we know that the + // left over entries from one BucketGroup will fit in the new BucketGroup. + if ( !handled0 || !handled1 || !handled2 || !handled3 ) { + // Rather than calling insert one time per entry + // Exploiting the fact that the new group is known to be empty + // And that BucketGroups are allowed to have holes in them (to allow for removal), + // so each unhandled entry from the source group is simply placed in + // the same slot in the new group + BucketGroup thisNewHashGroup = new BucketGroup(); + int hashFilter = 0; + if ( !handled0 ) { + thisNewHashGroup.hash0 = thatCurGroup.hash0; + thisNewHashGroup.entry0 = thatCurGroup.entry0; + hashFilter |= thatCurGroup.hash0; + } + if ( !handled1 ) { + thisNewHashGroup.hash1 = thatCurGroup.hash1; + thisNewHashGroup.entry1 = thatCurGroup.entry1; + hashFilter |= thatCurGroup.hash1; + } + if ( !handled2 ) { + thisNewHashGroup.hash2 = thatCurGroup.hash2; + thisNewHashGroup.entry2 = thatCurGroup.entry2; + hashFilter |= thatCurGroup.hash2; + } + if ( !handled3 ) { + thisNewHashGroup.hash3 = thatCurGroup.hash3; + thisNewHashGroup.entry3 = thatCurGroup.entry3; + hashFilter |= thatCurGroup.hash3; + } + // thisNewHashGroup.hashFilter = hashFilter; + thisNewHashGroup.prev = thisNewestHeadGroup; + + thisNewestHeadGroup = thisNewHashGroup; + } + } + + return thisNewestHeadGroup; + } + + boolean replaceOrInsertInChain(int hash, Entry entry) { + return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry); + } + + Entry replaceInChain(int hash, Entry entry) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + Entry prevEntry = curGroup._replace(hash, entry); + if ( prevEntry != null ) return prevEntry; + } + return null; + } + + Entry _replace(int hash, Entry entry) { + // if ( this._mayContain(hash) ) return null; + + // first check to see if the item is already present + Entry prevEntry = null; + if ( this.hash0 == hash && this.entry0.matches(entry.tag) ) { + prevEntry = this.entry0; + this.entry0 = entry; + } else if ( this.hash1 == hash && this.entry1.matches(entry.tag) ) { + prevEntry = this.entry1; + this.entry1 = entry; + } else if ( this.hash2 == hash && this.entry2.matches(entry.tag) ) { + prevEntry = this.entry2; + this.entry2 = entry; + } else if ( this.hash3 == hash && this.entry3.matches(entry.tag) ) { + prevEntry = this.entry3; + this.entry3 = entry; + } + + // no need to update this.hashFilter, since the hash is already included + return prevEntry; + } + + boolean insertInChain(int hash, Entry entry) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + if ( curGroup._insert(hash, entry) ) return true; + } + return false; + } + + boolean _insert(int hash, Entry entry) { + boolean inserted = false; + if ( this.hash0 == 0 ) { + this.hash0 = hash; + this.entry0 = entry; + + // this.hashFilter |= hash; + inserted = true; + } else if ( this.hash1 == 0 ) { + this.hash1 = hash; + this.entry1 = entry; + + // this.hashFilter |= hash; + inserted = true; + } else if ( this.hash2 == 0 ) { + this.hash2 = hash; + this.entry2 = entry; + + // this.hashFilter |= hash; + inserted = true; + } else if ( this.hash3 == 0 ) { + this.hash3 = hash; + this.entry3 = entry; + + // this.hashFilter |= hash; + inserted = true; + } + return inserted; + } + + BucketGroup removeGroupInChain(BucketGroup removeGroup) { + BucketGroup firstGroup = this; + if ( firstGroup == removeGroup ) { + return firstGroup.prev; + } + + for ( BucketGroup priorGroup = firstGroup, curGroup = priorGroup.prev; + curGroup != null; + priorGroup = curGroup, curGroup = priorGroup.prev ) { + if ( curGroup == removeGroup ) { + priorGroup.prev = curGroup.prev; + } + } + return firstGroup; + } + + Entry _remove(int hash, String tag) { + Entry existingEntry = null; + if ( this.hash0 == hash && this.entry0.matches(tag)) { + existingEntry = this.entry0; + + this.hash0 = 0; + this.entry0 = null; + } else if ( this.hash1 == hash && this.entry1.matches(tag) ) { + existingEntry = this.entry1; + + this.hash1 = 0; + this.entry1 = null; + } else if ( this.hash2 == hash && this.entry2.matches(tag) ) { + existingEntry = this.entry2; + + this.hash2 = 0; + this.entry2 = null; + } else if ( this.hash3 == hash && this.entry3.matches(tag) ) { + existingEntry = this.entry3; + + this.hash3 = 0; + this.entry3 = null; + } + return existingEntry; + } + + void forEachInChain(Consumer consumer) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._forEach(consumer); + } + } + + void _forEach(Consumer consumer) { + if ( this.entry0 != null ) consumer.accept(this.entry0); + if ( this.entry1 != null ) consumer.accept(this.entry1); + if ( this.entry2 != null ) consumer.accept(this.entry2); + if ( this.entry3 != null ) consumer.accept(this.entry3); + } + + void forEachInChain(T thisObj, BiConsumer consumer) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._forEach(thisObj, consumer); + } + } + + void _forEach(T thisObj, BiConsumer consumer) { + if ( this.entry0 != null ) consumer.accept(thisObj, this.entry0); + if ( this.entry1 != null ) consumer.accept(thisObj, this.entry1); + if ( this.entry2 != null ) consumer.accept(thisObj, this.entry2); + if ( this.entry3 != null ) consumer.accept(thisObj, this.entry3); + } + + void forEachInChain(T thisObj, U otherObj, TriConsumer consumer) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._forEach(thisObj, otherObj, consumer); + } + } + + void _forEach(T thisObj, U otherObj, TriConsumer consumer) { + if ( this.entry0 != null ) consumer.accept(thisObj, otherObj, this.entry0); + if ( this.entry1 != null ) consumer.accept(thisObj, otherObj, this.entry1); + if ( this.entry2 != null ) consumer.accept(thisObj, otherObj, this.entry2); + if ( this.entry3 != null ) consumer.accept(thisObj, otherObj, this.entry3); + } + + void fillMapFromChain(Map map) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._fillMap(map); + } + } + + void _fillMap(Map map) { + Entry entry0 = this.entry0; + if ( entry0 != null ) map.put(entry0.tag, entry0.objectValue()); + + Entry entry1 = this.entry1; + if ( entry1 != null ) map.put(entry1.tag, entry1.objectValue()); + + Entry entry2 = this.entry2; + if ( entry2 != null ) map.put(entry2.tag, entry2.objectValue()); + + Entry entry3 = this.entry3; + if ( entry3 != null ) map.put(entry3.tag, entry3.objectValue()); + } + + void fillStringMapFromChain(Map map) { + for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + curGroup._fillStringMap(map); + } + } + + void _fillStringMap(Map map) { + Entry entry0 = this.entry0; + if ( entry0 != null ) map.put(entry0.tag, entry0.stringValue()); + + Entry entry1 = this.entry1; + if ( entry1 != null ) map.put(entry1.tag, entry1.stringValue()); + + Entry entry2 = this.entry2; + if ( entry2 != null ) map.put(entry2.tag, entry2.stringValue()); + + Entry entry3 = this.entry3; + if ( entry3 != null ) map.put(entry3.tag, entry3.stringValue()); + } + + BucketGroup cloneChain() { + BucketGroup thisClone = this._cloneEntries(); + + BucketGroup thisPriorClone = thisClone; + for ( BucketGroup curGroup = this.prev; + curGroup != null; + curGroup = curGroup.prev ) + { + BucketGroup newClone = curGroup._cloneEntries(); + thisPriorClone.prev = newClone; + + thisPriorClone = newClone; + } + + return thisClone; + } + + BucketGroup _cloneEntries() { + return new BucketGroup( + this.hash0, this.entry0, + this.hash1, this.entry1, + this.hash2, this.entry2, + this.hash3, this.entry3); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(32); + builder.append('['); + for ( int i = 0; i < BucketGroup.LEN; ++i ) { + if ( builder.length() != 0 ) builder.append(", "); + + builder.append(this._entryAt(i)); + } + builder.append(']'); + return builder.toString(); + } + } + + private static final class Entries extends AbstractSet> { + private final TagMap map; + + Entries(TagMap map) { + this.map = map; + } + + @Override + public int size() { + return this.map.computeSize(); + } + + @Override + public boolean isEmpty() { + return this.map.checkIfEmpty(); + } + + @Override + public Iterator> iterator() { + @SuppressWarnings({"rawtypes", "unchecked"}) + Iterator> iter = (Iterator)this.map.iterator(); + return iter; + } + } + + private static final class Keys extends AbstractSet { + private final TagMap map; + + Keys(TagMap map) { + this.map = map; + } + + @Override + public int size() { + return this.map.computeSize(); + } + + @Override + public boolean isEmpty() { + return this.map.checkIfEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.map.containsKey(o); + } + + @Override + public Iterator iterator() { + return new KeysIterator(this.map); + } + } + + static final class KeysIterator extends MapIterator { + KeysIterator(TagMap map) { + super(map); + } + + @Override + public String next() { + return this.nextEntry().tag(); + } + } + + private static final class Values extends AbstractCollection { + private final TagMap map; + + Values(TagMap map) { + this.map = map; + } + + @Override + public int size() { + return this.map.computeSize(); + } + + @Override + public boolean isEmpty() { + return this.map.checkIfEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.map.containsValue(o); + } + + @Override + public Iterator iterator() { + return new ValuesIterator(this.map); + } + } + + static final class ValuesIterator extends MapIterator { + ValuesIterator(TagMap map) { + super(map); + } + + @Override + public Object next() { + return this.nextEntry().objectValue(); + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java index 28ddbe27ad3..296b93d0914 100644 --- a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java +++ b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java @@ -1,5 +1,6 @@ package datadog.trace.api.gateway; +import datadog.trace.api.TagMap; import datadog.trace.api.DDTraceId; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import java.util.Map; @@ -9,7 +10,7 @@ public interface IGSpanInfo { long getSpanId(); - Map getTags(); + TagMap getTags(); AgentSpan setTag(String key, boolean value); 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 63cd5afd2c9..ef102de8e5f 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,5 +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; @@ -229,11 +230,10 @@ interface ForPeerService { /** * Calculate the tags to be added to a span to represent the peer service * - * @param unsafeTags the span tags. Map che be mutated - * @return the input tags + * @param unsafeTags the span tags. Map to be mutated */ @Nonnull - Map tags(@Nonnull Map unsafeTags); + void tags(@Nonnull TagMap 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 b68496cc1f9..282261772e6 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,5 +1,6 @@ package datadog.trace.api.naming.v0; +import datadog.trace.api.TagMap; import datadog.trace.api.naming.NamingSchema; import java.util.Collections; import java.util.Map; @@ -13,7 +14,6 @@ public boolean supports() { @Nonnull @Override - public Map tags(@Nonnull final Map unsafeTags) { - return Collections.emptyMap(); + public void tags(@Nonnull final TagMap 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 47ff1ad9d29..4c64dda3a73 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,6 +1,7 @@ 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; @@ -52,8 +53,8 @@ public boolean supports() { return true; } - private void resolve(@Nonnull final Map unsafeTags) { - final Object component = unsafeTags.get(Tags.COMPONENT); + private void resolve(@Nonnull final TagMap unsafeTags) { + final Object component = unsafeTags.getObject(Tags.COMPONENT); // avoid issues with UTF8ByteString or others final String componentString = component == null ? null : component.toString(); final String override = overridesByComponent.get(componentString); @@ -71,14 +72,14 @@ private void resolve(@Nonnull final Map unsafeTags) { } private boolean resolveBy( - @Nonnull final Map unsafeTags, @Nullable final String[] precursors) { + @Nonnull final TagMap unsafeTags, @Nullable final String[] precursors) { if (precursors == null) { return false; } Object value = null; String source = null; for (String precursor : precursors) { - value = unsafeTags.get(precursor); + value = unsafeTags.getObject(precursor); if (value != null) { // we have a match. Use the tag name for the source source = precursor; @@ -90,7 +91,7 @@ private boolean resolveBy( return true; } - private void set(@Nonnull final Map unsafeTags, Object value, String source) { + private void set(@Nonnull final TagMap unsafeTags, Object value, String source) { if (value != null) { unsafeTags.put(Tags.PEER_SERVICE, value); unsafeTags.put(DDTags.PEER_SERVICE_SOURCE, source); @@ -99,13 +100,12 @@ private void set(@Nonnull final Map unsafeTags, Object value, St @Nonnull @Override - public Map tags(@Nonnull final Map unsafeTags) { + public void tags(@Nonnull final TagMap unsafeTags) { // check span.kind eligibility - final Object kind = unsafeTags.get(Tags.SPAN_KIND); + final Object kind = unsafeTags.getObject(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); } - return unsafeTags; } } diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java index 412ac7c85cc..ea62a78008d 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java @@ -2,6 +2,7 @@ import static datadog.trace.bootstrap.instrumentation.api.InternalContextKeys.SPAN_KEY; +import datadog.trace.api.TagMap; import datadog.context.Context; import datadog.context.ContextKey; import datadog.context.ImplicitContextKeyed; @@ -11,6 +12,8 @@ import datadog.trace.api.gateway.IGSpanInfo; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.interceptor.MutableSpan; + +import java.util.Collections; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -91,6 +94,9 @@ default boolean isValid() { @Override AgentSpan setSpanType(final CharSequence type); + @Override + TagMap getTags(); + Object getTag(String key); @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java index 84b9d55ecd1..7754a1f92de 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java @@ -1,6 +1,7 @@ package datadog.trace.bootstrap.instrumentation.api; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; @@ -108,19 +109,18 @@ public boolean isOutbound() { @Override public Object getTag(final String tag) { if (this.spanContext instanceof TagContext) { - return ((TagContext) this.spanContext).getTags().get(tag); + return ((TagContext) this.spanContext).getTags().getObject(tag); } return null; } @Override - public Map getTags() { + public TagMap getTags() { if (this.spanContext instanceof TagContext) { - Map tags = ((TagContext) this.spanContext).getTags(); - //noinspection unchecked,rawtypes - return (Map) tags; + return ((TagContext) this.spanContext).getTags(); + } else { + return TagMap.EMPTY; } - return Collections.emptyMap(); } @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java index 5d476c39b20..7cb834ca520 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java @@ -1,9 +1,8 @@ package datadog.trace.bootstrap.instrumentation.api; -import static java.util.Collections.emptyMap; - import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; @@ -79,10 +78,10 @@ public Integer getSamplingPriority() { public String getSpanType() { return null; } - + @Override - public Map getTags() { - return emptyMap(); + public TagMap getTags() { + return TagMap.EMPTY; } @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index f5185d6292c..4c89d6a45aa 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -3,6 +3,7 @@ import static datadog.trace.api.TracePropagationStyle.NONE; import static java.util.Collections.emptyList; +import datadog.trace.api.TagMap; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; import datadog.trace.api.TraceConfig; @@ -24,7 +25,7 @@ public class TagContext implements AgentSpanContext.Extracted { private static final HttpHeaders EMPTY_HTTP_HEADERS = new HttpHeaders(); private final CharSequence origin; - private Map tags; + private TagMap tags; private List terminatedContextLinks; private Object requestContextDataAppSec; private Object requestContextDataIast; @@ -41,19 +42,22 @@ public TagContext() { this(null, null); } - public TagContext(final CharSequence origin, final Map tags) { + public TagContext(final CharSequence origin, final TagMap tags) { this(origin, tags, null, null, PrioritySampling.UNSET, null, NONE, DDTraceId.ZERO); } public TagContext( final CharSequence origin, - final Map tags, + final TagMap tags, final HttpHeaders httpHeaders, final Map baggage, final int samplingPriority, final TraceConfig traceConfig, final TracePropagationStyle propagationStyle, final DDTraceId traceId) { + + //if ( tags != null ) tags.checkWriteAccess(); + this.origin = origin; this.tags = tags; this.terminatedContextLinks = null; @@ -164,15 +168,16 @@ public String getCustomIpHeader() { return httpHeaders.customIpHeader; } - public final Map getTags() { - return tags; + public final TagMap getTags() { + // DQH - Because of the lazy in putTag, this method effectively returns an immutable map + return ( this.tags == null ) ? TagMap.EMPTY: this.tags; } public void putTag(final String key, final String value) { - if (this.tags.isEmpty()) { - this.tags = new TreeMap<>(); - } - this.tags.put(key, value); + if ( this.tags == null ) { + this.tags = new TagMap(); + } + this.tags.set(key, value); } @Override diff --git a/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy b/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy index ca1957ad6e0..7cdc25a22d9 100644 --- a/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/bootstrap/instrumentation/api/ExtractedSpanTest.groovy @@ -1,12 +1,13 @@ package datadog.trace.bootstrap.instrumentation.api import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap import spock.lang.Specification class ExtractedSpanTest extends Specification { def 'test extracted span from partial tracing context'() { given: - def tags = ['tag-1': 'value-1', 'tag-2': 'value-2'] + def tags = TagMap.fromMap(['tag-1': 'value-1', 'tag-2': 'value-2']) def baggage = ['baggage-1': 'value-1', 'baggage-2': 'value-2'] def traceId = DDTraceId.from(12345) def context = new TagContext('origin', tags, null, baggage, 0, null, null, traceId) From 97bbb2c180acac5fa5233b05cd7d8427080928a0 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 18 Mar 2025 15:01:10 -0400 Subject: [PATCH 30/84] Making more locals final for consistency --- .../main/java/datadog/trace/core/CoreTracer.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 33aa866fd79..7bf7ba326b3 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 @@ -1499,7 +1499,9 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { } TagMap.Builder tagBuilder = this.tagBuilder; if (tagBuilder == null) { - this.tagBuilder = tagBuilder = TagMap.builder(); // Insertion order is important + // Insertion order is important, so using TagBuilder which builds up a set + // of Entry modifications in order + this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { tagBuilder.remove(tag); @@ -1557,13 +1559,13 @@ private DDSpanContext buildSpanContext() { final int samplingPriority; final CharSequence origin; final TagMap coreTags; - boolean coreTagsNeedsIntercept; + final boolean coreTagsNeedsIntercept; final TagMap rootSpanTags; - boolean rootSpanTagsNeedsIntercept; + final boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; - Object requestContextDataAppSec; - Object requestContextDataIast; - Object ciVisibilityContextData; + final Object requestContextDataAppSec; + final Object requestContextDataIast; + final Object ciVisibilityContextData; final PathwayContext pathwayContext; final PropagationTags propagationTags; From d296a7a929c6e515e16ad29f98c1d0e12031b333 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 18 Mar 2025 15:08:24 -0400 Subject: [PATCH 31/84] Taking final off of the product specific helper locals for now --- .../java/datadog/trace/core/CoreTracer.java | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) 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 7bf7ba326b3..024f83d6f5b 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 @@ -949,7 +949,7 @@ long getTimeWithNanoTicks(long nanoTicks) { @Override public CoreSpanBuilder buildSpan( final String instrumentationName, final CharSequence operationName) { - return new CoreSpanBuilder(instrumentationName, operationName, this); + return new CoreSpanBuilder(this, instrumentationName, operationName); } @Override @@ -1360,7 +1360,7 @@ private static Map invertMap(Map map) { } /** Spans are built using this builder */ - public class CoreSpanBuilder implements AgentTracer.SpanBuilder { + public static class CoreSpanBuilder implements AgentTracer.SpanBuilder { private final String instrumentationName; private final CharSequence operationName; private final CoreTracer tracer; @@ -1381,7 +1381,10 @@ public class CoreSpanBuilder implements AgentTracer.SpanBuilder { private long spanId; CoreSpanBuilder( - final String instrumentationName, final CharSequence operationName, CoreTracer tracer) { + final CoreTracer tracer, + final String instrumentationName, + final CharSequence operationName) + { this.instrumentationName = instrumentationName; this.operationName = operationName; this.tracer = tracer; @@ -1422,7 +1425,7 @@ private void addTerminatedContextAsLinks() { public AgentSpan start() { AgentSpanContext pc = parent; if (pc == null && !ignoreScope) { - final AgentSpan span = activeSpan(); + final AgentSpan span = tracer.activeSpan(); if (span != null) { pc = span.context(); } @@ -1563,14 +1566,14 @@ private DDSpanContext buildSpanContext() { final TagMap rootSpanTags; final boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; - final Object requestContextDataAppSec; - final Object requestContextDataIast; - final Object ciVisibilityContextData; + Object requestContextDataAppSec; + Object requestContextDataIast; + Object ciVisibilityContextData; final PathwayContext pathwayContext; final PropagationTags propagationTags; if (this.spanId == 0) { - spanId = idGenerationStrategy.generateSpanId(); + spanId = tracer.idGenerationStrategy.generateSpanId(); } else { spanId = this.spanId; } @@ -1578,7 +1581,7 @@ private DDSpanContext buildSpanContext() { AgentSpanContext parentContext = parent; if (parentContext == null && !ignoreScope) { // use the Scope as parent unless overridden or ignored. - final AgentSpan activeSpan = scopeManager.activeSpan(); + final AgentSpan activeSpan = tracer.scopeManager.activeSpan(); if (activeSpan != null) { parentContext = activeSpan.context(); } @@ -1638,7 +1641,7 @@ private DDSpanContext buildSpanContext() { requestContextDataIast = null; ciVisibilityContextData = null; } - propagationTags = propagationTagsFactory.empty(); + propagationTags = tracer.propagationTagsFactory.empty(); } else { long endToEndStartTime; @@ -1654,19 +1657,19 @@ private DDSpanContext buildSpanContext() { } else if (parentContext != null) { traceId = parentContext.getTraceId() == DDTraceId.ZERO - ? idGenerationStrategy.generateTraceId() + ? tracer.idGenerationStrategy.generateTraceId() : parentContext.getTraceId(); parentSpanId = parentContext.getSpanId(); samplingPriority = parentContext.getSamplingPriority(); endToEndStartTime = 0; - propagationTags = propagationTagsFactory.empty(); + propagationTags = tracer.propagationTagsFactory.empty(); } else { // Start a new trace - traceId = idGenerationStrategy.generateTraceId(); + traceId = tracer.idGenerationStrategy.generateTraceId(); parentSpanId = DDSpanId.ZERO; samplingPriority = PrioritySampling.UNSET; endToEndStartTime = 0; - propagationTags = propagationTagsFactory.empty(); + propagationTags = tracer.propagationTagsFactory.empty(); } ConfigSnapshot traceConfig; @@ -1693,10 +1696,10 @@ private DDSpanContext buildSpanContext() { ciVisibilityContextData = null; } - rootSpanTags = localRootSpanTags; - rootSpanTagsNeedsIntercept = localRootSpanTagsNeedIntercept; + rootSpanTags = tracer.localRootSpanTags; + rootSpanTagsNeedsIntercept = tracer.localRootSpanTagsNeedIntercept; - parentTraceCollector = createTraceCollector(traceId, traceConfig); + parentTraceCollector = tracer.createTraceCollector(traceId, traceConfig); if (endToEndStartTime > 0) { parentTraceCollector.beginEndToEnd(endToEndStartTime); @@ -1711,11 +1714,11 @@ private DDSpanContext buildSpanContext() { && parentContext.getPathwayContext() != null && parentContext.getPathwayContext().isStarted() ? parentContext.getPathwayContext() - : dataStreamsMonitoring.newPathwayContext(); + : tracer.dataStreamsMonitoring.newPathwayContext(); // when removing fake services the best upward service name to pick is the local root one // since a split by tag (i.e. servlet context) might have happened on it. - if (!allowInferredServices) { + if (!tracer.allowInferredServices) { final DDSpan rootSpan = parentTraceCollector.getRootSpan(); serviceName = rootSpan != null ? rootSpan.getServiceName() : null; } @@ -1739,7 +1742,7 @@ private DDSpanContext buildSpanContext() { if (serviceName == null) { // it could be on the initial snapshot but may be overridden to null and service name // cannot be null - serviceName = CoreTracer.this.serviceName; + serviceName = tracer.serviceName; } final CharSequence operationName = @@ -1787,10 +1790,10 @@ private DDSpanContext buildSpanContext() { requestContextDataIast, ciVisibilityContextData, pathwayContext, - disableSamplingMechanismValidation, + tracer.disableSamplingMechanismValidation, propagationTags, - profilingContextIntegration, - injectBaggageAsTags, + tracer.profilingContextIntegration, + tracer.injectBaggageAsTags, isRemote); // By setting the tags on the context we apply decorators to any tags that have been set via From b5ce9a7024eabab99ad945edf5c69aff08243318 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 10:46:47 -0400 Subject: [PATCH 32/84] Fixed bug in TagMap$BucketGroup#_isEmpty This bug causes remove to incorrectly remove a whole BucketGroup when the BucketGroup still contains entries. The intention was opposite only empty BucketGroups should be removed from the chain. Incorporated tests from prototype that caught this issue. --- .../main/java/datadog/trace/api/TagMap.java | 2 +- .../datadog/trace/api/TagMapBuilderTest.java | 106 +++++ .../datadog/trace/api/TagMapEntryTest.java | 258 ++++++++++ .../datadog/trace/api/TagMapFuzzTest.java | 444 ++++++++++++++++++ .../java/datadog/trace/api/TagMapTest.java | 371 +++++++++++++++ 5 files changed, 1180 insertions(+), 1 deletion(-) create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapTest.java diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 5b7b0b45934..f0839e1aacc 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1592,7 +1592,7 @@ boolean isEmptyChain() { } boolean _isEmpty() { - return (this.hash0 | this.hash1 | this.hash2 | this.hash3) != 0; + return (this.hash0 | this.hash1 | this.hash2 | this.hash3) == 0; // return (this.hashFilter == 0); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java new file mode 100644 index 00000000000..987a2507551 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -0,0 +1,106 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TagMapBuilderTest { + static final int SIZE = 32; + + @Test + public void buildMutable() { + TagMap.Builder builder = TagMap.builder(); + for ( int i = 0; i < SIZE; ++i ) { + builder.put(key(i), value(i)); + } + + assertEquals(SIZE, builder.estimateSize()); + + TagMap map = builder.build(); + for ( int i = 0; i < SIZE; ++i ) { + assertEquals(value(i), map.getString(key(i))); + } + assertEquals(SIZE, map.computeSize()); + + // just proving that the map is mutable + map.set(key(1000), value(1000)); + } + + @Test + public void buildImmutable() { + TagMap.Builder builder = TagMap.builder(); + for ( int i = 0; i < SIZE; ++i ) { + builder.put(key(i), value(i)); + } + + assertEquals(SIZE, builder.estimateSize()); + + TagMap map = builder.buildImmutable(); + for ( int i = 0; i < SIZE; ++i ) { + assertEquals(value(i), map.getString(key(i))); + } + assertEquals(SIZE, map.computeSize()); + + assertFrozen(map); + } + + @Test + public void buildWithRemoves() { + TagMap.Builder builder = TagMap.builder(); + for ( int i = 0; i < SIZE; ++i ) { + builder.put(key(i), value(i)); + } + + for ( int i = 0; i < SIZE; i += 2 ) { + builder.remove(key(i)); + } + + TagMap map = builder.build(); + for ( int i = 0; i < SIZE; ++i ) { + if ( (i % 2) == 0 ) { + assertNull(map.getString(key(i))); + } else { + assertEquals(value(i), map.getString(key(i))); + } + } + } + + @Test + public void reset() { + TagMap.Builder builder = TagMap.builder(2); + + builder.put(key(0), value(0)); + TagMap map0 = builder.build(); + + builder.reset(); + + builder.put(key(1), value(1)); + TagMap map1 = builder.build(); + + assertEquals(value(0), map0.getString(key(0))); + assertNull(map1.getString(key(0))); + + assertNull(map0.getString(key(1))); + assertEquals(value(1), map1.getString(key(1))); + } + + static final String key(int i) { + return "key-" + i; + } + + static final String value(int i) { + return "value-" + i; + } + + static final void assertFrozen(TagMap map) { + IllegalStateException ex = null; + try { + map.put("foo", "bar"); + } catch ( IllegalStateException e ) { + ex = e; + } + assertNotNull(ex); + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java new file mode 100644 index 00000000000..cfb1b9c4929 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -0,0 +1,258 @@ +package datadog.trace.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.Test; + +public class TagMapEntryTest { + @Test + public void objectEntry() { + TagMap.Entry entry = TagMap.Entry.newObjectEntry("foo", "bar"); + assertKey("foo", entry); + assertValue("bar", entry); + + assertEquals("bar", entry.stringValue()); + + assertTrue(entry.isObject()); + } + + @Test + public void anyEntry_object() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", "bar"); + + assertKey("foo", entry); + assertValue("bar", entry); + + assertTrue(entry.isObject()); + + assertKey("foo", entry); + assertValue("bar", entry); + } + + @Test + public void booleanEntry() { + TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", true); + + assertKey("foo", entry); + assertValue(true, entry); + + assertFalse(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + } + + @Test + public void booleanEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); + + assertKey("foo", entry); + assertValue(true, entry); + + assertFalse(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + } + + @Test + public void anyEntry_boolean() { + TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); + + assertKey("foo", entry); + assertValue(true, entry); + + assertFalse(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + + assertValue(true, entry); + } + + @Test + public void intEntry() { + TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", 20); + + assertKey("foo", entry); + assertValue(20, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.INT)); + } + + @Test + public void intEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)); + + assertKey("foo", entry); + assertValue(20, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.INT)); + } + + @Test + public void anyEntry_int() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)); + + assertKey("foo", entry); + assertValue(20, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.INT)); + + assertValue(20, entry); + } + + @Test + public void longEntry() { + TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", 1_048_576L); + + assertKey("foo", entry); + assertValue(1_048_576L, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.LONG)); + } + + @Test + public void longEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)); + + assertKey("foo", entry); + assertValue(1_048_576L, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.LONG)); + } + + @Test + public void anyEntry_long() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)); + + assertKey("foo", entry); + assertValue(1_048_576L, entry); + + // type checks force any resolution + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.LONG)); + + // check value again after resolution + assertValue(1_048_576L, entry); + } + + @Test + public void doubleEntry() { + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); + + assertKey("foo", entry); + assertValue(Math.PI, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.DOUBLE)); + } + + @Test + public void doubleEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); + + assertKey("foo", entry); + assertValue(Math.PI, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.DOUBLE)); + } + + @Test + public void anyEntry_double() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)); + + assertKey("foo", entry); + assertValue(Math.PI, entry); + + // type checks force any resolution + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.DOUBLE)); + + // check value again after resolution + assertValue(Math.PI, entry); + } + + @Test + public void floatEntry() { + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); + + assertKey("foo", entry); + assertValue(2.718281828f, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.FLOAT)); + } + + @Test + public void floatEntry_boxed() { + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); + + assertKey("foo", entry); + assertValue(2.718281828f, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.FLOAT)); + } + + static final void assertKey(String expected, TagMap.Entry entry) { + assertEquals(expected, entry.tag()); + assertEquals(expected, entry.getKey()); + } + + static final void assertValue(Object expected, TagMap.Entry entry) { + assertEquals(expected, entry.objectValue()); + assertEquals(expected, entry.getValue()); + + assertEquals(expected.toString(), entry.stringValue()); + } + + static final void assertValue(boolean expected, TagMap.Entry entry) { + assertEquals(expected, entry.booleanValue()); + assertEquals(Boolean.valueOf(expected), entry.objectValue()); + + assertEquals(Boolean.toString(expected), entry.stringValue()); + } + + static final void assertValue(int expected, TagMap.Entry entry) { + assertEquals(expected, entry.intValue()); + assertEquals((long)expected, entry.longValue()); + assertEquals((float)expected, entry.floatValue()); + assertEquals((double)expected, entry.doubleValue()); + assertEquals(Integer.valueOf(expected), entry.objectValue()); + + assertEquals(Integer.toString(expected), entry.stringValue()); + } + + static final void assertValue(long expected, TagMap.Entry entry) { + assertEquals(expected, entry.longValue()); + assertEquals((int)expected, entry.intValue()); + assertEquals((float)expected, entry.floatValue()); + assertEquals((double)expected, entry.doubleValue()); + assertEquals(Long.valueOf(expected), entry.objectValue()); + + assertEquals(Long.toString(expected), entry.stringValue()); + } + + static final void assertValue(double expected, TagMap.Entry entry) { + assertEquals(expected, entry.doubleValue()); + assertEquals((int)expected, entry.intValue()); + assertEquals((long)expected, entry.longValue()); + assertEquals((float)expected, entry.floatValue()); + assertEquals(Double.valueOf(expected), entry.objectValue()); + + assertEquals(Double.toString(expected), entry.stringValue()); + } + + static final void assertValue(float expected, TagMap.Entry entry) { + assertEquals(expected, entry.floatValue()); + assertEquals((int)expected, entry.intValue()); + assertEquals((long)expected, entry.longValue()); + assertEquals((double)expected, entry.doubleValue()); + assertEquals(Float.valueOf(expected), entry.objectValue()); + + assertEquals(Float.toString(expected), entry.stringValue()); + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java new file mode 100644 index 00000000000..053997f7bd4 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -0,0 +1,444 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.Test; + +public final class TagMapFuzzTest { + static final int NUM_KEYS = 128; + static final int MAX_NUM_ACTIONS = 32; + + @Test + void test() { + test(generateTest()); + } + + @Test + void testMerge() { + TestCase mapACase = generateTest(); + TestCase mapBCase = generateTest(); + + TagMap tagMapA = test(mapACase); + TagMap tagMapB = test(mapBCase); + + HashMap hashMapA = new HashMap<>(tagMapA); + HashMap hashMapB = new HashMap<>(tagMapB); + + tagMapA.putAll(tagMapB); + hashMapA.putAll(hashMapB); + + assertMapEquals(hashMapA, tagMapA); + } + + @Test + void priorFailingCase0() { + TagMap map = makeTagMap( + remove("key-4"), + put("key-71","values-443049055"), + put("key-2","values-1227065898"), + put("key-25","values-696891692"), + put("key-93","values-763707175"), + put("key-23","values--1514091210"), + put("key-16","values--1388742686") + ); + + MapAction failingAction = putAllTagMap("key-17","values--2085338893","key-51","values-960243765","key-33","values-1493544499","key-46","values-697926849","key-70","values--184054454","key-67","values-374577326","key-9","values--742453833","key-11","values-1606950841","key-119","values--1914593057","key-53","values-375236438","key-96","values--107185569","key-47","values--1276407408","key-125","values--1627172151","key-110","values--1227150283","key-15","values-380379920","key-42","values--632271048","key-99","values--650090786","key-8","values--1990889145","key-103","values-1815698254","key-120","values-279025031","key-93","values-589795963","key-12","values--935895941","key-105","values-94976227","key-85","values--424609970","key-78","values-1231948102","key-115","values-88670282","key-26","values-733903384","key-100","values-2102967487","key-74","values-958598087","key-104","values-264458254","key-125","values--1781797927","key-27","values--562810078","key-7","values--376776745","key-111","values-263564677","key-50","values--859673100","key-57","values-1585057281","key-48","values--617889787","key-98","values--1878108220","key-9","values--227223375","key-59","values-1577082288","key-94","values--268049040","key-0","values-1708355496","key-62","values--733451297","key-14","values-232732747","key-4","values--406605642","key-58","values-1772476833","key-8","values--1155025225","key-101","values-144480545","key-66","values-355117269","key-121","values-1858008722","key-33","values-1947754079","key-1","values--1475603838","key-125","values--2146772243","key-117","values-852022714","key-53","values--2039348506","key-65","values-2011228657","key-108","values-1581592518","key-17","values-2129571020","key-5","values-1106900841","key-80","values-1791757923","key-18","values--1992962227","key-2","values-328863878","key-110","values-1182949334","key-5","values-1049403346","key-107","values-1246502060","key-115","values-2053931423","key-19","values--1731179633","key-104","values--1090790550","key-67","values--1312759979","key-10","values-1411135","key-109","values--1784920248","key-20","values--827644780","key-55","values--1610270998","key-60","values-1287959520","key-31","values-1686541667","key-41","values-399844058","key-115","values-2045201464","key-78","values-358081227","key-57","values--1374149269","key-65","values-1871734555","key-124","values--211494558","key-119","values-1757597102","key-32","values--336988038","key-85","values-1415155858","key-44","values-1455425178","key-48","values--325658059","key-68","values--793590840","key-96","values--2010766492","key-40","values-2007171160","key-29","values-186945230","key-63","values-1741962849","key-26","values-948582805","key-31","values-47004766","key-90","values-1304302008","key-69","values-2120328211","key-111","values-2053321468","key-69","values--498524858","key-125","values--193004619","key-30","values--1142090845","key-15","values--1334900170","key-33","values-1011001500","key-55","values-452401605","key-18","values-1260118555","key-44","values--1109396459","key-2","values--555647718","key-61","values-1060742038","key-51","values--827099230","key-62","values--1443716296","key-16","values-534556355","key-81","values--787910427","key-20","values-1429697120","key-36","values--1775988293","key-66","values-624669635","key-25","values--684183265","key-26","values-293626449","key-91","values--1212867803","key-6","values-1778251481","key-83","values-1257370908","key-92","values--1120490028","key-111","values-9646496","key-90","values-1485206899"); + failingAction.apply(map); + failingAction.verify(map); + } + + @Test + void priorFailingCase1() { + TagMap map = makeTagMap( + put("key-68","values--37178328"), + put("key-93","values--2093086281") + ); + + MapAction failingAction = putAllTagMap("key-36","values--1951535044","key-59","values--1045985660","key-68","values-1270827526","key-65","values-440073158","key-91","values-954365843","key-75","values-1014366449","key-117","values--1306617705","key-90","values-984567966","key-120","values--1802603599","key-56","values-319574488","key-78","values--711288173","key-103","values-694279462","key-84","values-1391260657","key-59","values--484807195","key-67","values-1675498322","key-91","values--227731796","key-105","values--1471022333","key-112","values--755617374","key-117","values--668324524","key-65","values-1165174761","key-13","values--1947081814","key-72","values-2032502631","key-106","values-256372025","key-71","values--995163162","key-92","values-972782926","key-116","values-25012447","key-23","values--979671053","key-94","values-367125724","key-48","values--2011523144","key-14","values-578926680","key-65","values-1325737627","key-89","values-1539092266","key-100","values--319629978","key-53","values-1125496255","key-2","values-1988036327","key-105","values--1333468536","key-37","values-351345678","key-4","values-683252782","key-62","values--1466612877","key-100","values-268100559","key-104","values-3517495","key-48","values--1588410835","key-42","values--180653405","key-118","values--1181647255","key-17","values-509279769","key-33","values-298668287","key-76","values-2062435628","key-18","values-287811864","key-46","values--1337930894","key-50","values-2089310564","key-24","values--1870293199","key-47","values--1155431370","key-81","values--1507929564","key-115","values-1149614815","key-57","values--334611395","key-86","values-146447703","key-107","values-938082683","key-38","values-338654203","key-40","values--376260149","key-20","values--860844060","key-20","values-2003129702","key-75","values--1787311067","key-39","values--1988768973","key-58","values--479797619","key-16","values-571033631","key-65","values--1867296166","key-56","values--2071960469","key-12","values-821930484","key-40","values--54692885","key-65","values-328817493","key-121","values-1276016318","key-33","values--2081652233","key-31","values-381335133","key-77","values-1486312656","key-48","values--1058365372","key-109","values--733344537","key-85","values-1236864082","key-35","values-2045087594","key-49","values-1990762822","key-38","values--1582706513","key-18","values--626997990","key-80","values--1995264473","key-126","values--558193472","key-83","values-415016167","key-53","values-1348674948","key-58","values-612738550","key-12","values-417676134","key-101","values--58098778","key-127","values-1658306930","key-17","values-985378289","key-68","values-686600535","key-36","values-365513638","key-87","values--1737233661","key-67","values--1840935230","key-8","values-540289596","key-11","values--2045114386","key-38","values--786598887","key-48","values-1877144385","key-5","values-65838542","key-18","values-263200779","key-120","values--1500947489","key-65","values-769990109","key-38","values-1886840000","key-29","values--48760205","key-61","values--1942966789"); + failingAction.apply(map); + failingAction.verify(map); + } + + @Test + void priorFailingCase2() { + TestCase testCase = new TestCase( + remove("key-34"), + put("key-122","values-1828753938"), + putAll("key-123","values--118789056","key-28","values--751841781","key-105","values-1663318183","key-63","values--2036414463","key-74","values-1584612783","key-118","values--414681411","key-67","values-1154668404","key-1","values--1755856616","key-89","values--344740102","key-110","values-1884649283","key-1","values--1420345075","key-22","values-1951712698","key-103","values-488559164","key-8","values-1180668912","key-44","values-290310046","key-105","values--303926067","key-26","values-910376351","key-59","values-1600204544","key-23","values-425861746","key-76","values--1045446587","key-21","values-453905226","key-1","values-286624672","key-69","values-934359656","key-57","values--1890465763","key-13","values--1949062639","key-68","values-242077328","key-42","values--1584075743","key-46","values--1306318288","key-31","values--848418043","key-71","values--1547961101","key-121","values--1493693636","key-24","values-330660358","key-24","values--1466871690","key-91","values--995064376","key-18","values-1615316779","key-124","values--296191510","key-52","values-740309054","key-8","values-1777392898","key-73","values-92831985","key-13","values--1711360891","key-114","values-1960346620","key-44","values--1599497099","key-107","values-668485357","key-116","values--1792788504"), + put("key-123","values--1844485682"), + putAll("key-64","values--1694520036","key-17","values--469732912","key-79","values--1293521097","key-11","values--2000592955","key-98","values-517073723","key-28","values-1085152681","key-34","values-1943586726","key-3","values-216087991","key-97","values-222660872","key-41","values-90906196","key-63","values--934208984","key-57","values-327167184","key-111","values--1059115125","key-75","values--2031064209","key-8","values-1924310140","key-69","values--362514182","key-90","values-852043703","key-98","values--998302860","key-49","values-1658920804","key-106","values--227162298","key-25","values-493046373","key-52","values--555623542","key-77","values--717275660","key-31","values-1930766287","key-69","values--1367213079","key-38","values--1112081116","key-65","values--1916889923","key-96","values-157036191","key-127","values--302553995","key-38","values-485874872","key-110","values--855874569","key-39","values--390829775","key-7","values--452123269","key-63","values--527204905","key-101","values-166173307","key-126","values-1050454498","key-4","values--215188400","key-25","values-947961204","key-42","values-145803888","key-1","values--970532578","key-43","values--1675493776","key-29","values-1193328809","key-108","values-1302659140","key-120","values--1722764270","key-24","values--483238806","key-53","values-611589672","key-39","values--229429656","key-29","values--733337788","key-9","values-736222322","key-74","values--950770749","key-91","values-202817768","key-95","values-500260096","key-71","values--1798188865","key-12","values--1936098297","key-28","values--2116134632","key-21","values-799594067","key-68","values--333178107","key-50","values-445767791","key-88","values-1307699662","key-69","values--110615017","key-25","values-699603233","key-101","values--2093413536","key-91","values--2022040839","key-45","values-888546703","key-40","values--2140684954","key-1","values-371033654","key-68","values--20293415","key-59","values-697437101","key-43","values--1145022834","key-62","values--2125187195","key-15","values--1062944166","key-103","values--889634836","key-125","values-8694763","key-101","values--281475498","key-13","values-1972488719","key-32","values-1900833863","key-119","values--926978044","key-82","values-288820151","key-78","values--303310027","key-25","values--1284661437","key-47","values-1624726045","key-14","values-1658036950","key-65","values-1629683219","key-10","values-275264679","key-126","values--592085694","key-32","values-1844385705","key-85","values--1815321660","key-72","values-918231225","key-91","values-675699466","key-121","values--2008685332","key-61","values--1398921570","key-19","values-617817427","key-122","values--793708860","key-41","values--2027225350","key-41","values-1194206680","key-1","values-1116090448","key-49","values-1662444555","key-54","values-747436284","key-118","values--1367237858","key-65","values-133495093","key-73","values--1451855551","key-43","values--357794833","key-76","values-129403123","key-59","values--65688873","key-22","values-480031738","key-73","values--310815862","key-0","values--1734944386","key-56","values--540459893","key-38","values-1308912555","key-2","values--2073028093","key-14","values--693713438","key-76","values-295450436","key-113","values--2065146687","key-0","values-2076623027","key-17","values--1394046356","key-78","values--2014478659","key-5","values--665180960"), + put("key-124","values-460160716"), + put("key-112","values--1828904046"), + put("key-41","values--904162962")); + + Map expected = makeMap(testCase); + TagMap actual = makeTagMap(testCase); + + MapAction failingAction = remove("key-127"); + failingAction.apply(expected); + failingAction.verify(expected); + + failingAction.apply(actual); + failingAction.verify(actual); + + assertMapEquals(expected, actual); + } + + public static final TagMap test(MapAction... actions) { + return test(new TestCase(Arrays.asList(actions))); + } + + public static final Map makeMap(TestCase testCase) { + return makeMap(testCase.actions); + } + + public static final Map makeMap(MapAction... actions) { + return makeMap(Arrays.asList(actions)); + } + + public static final Map makeMap(List actions) { + Map map = new HashMap<>(); + for ( MapAction action: actions ) { + action.apply(map); + } + return map; + } + + public static final TagMap makeTagMap(TestCase testCase) { + return makeTagMap(testCase.actions); + } + + public static final TagMap makeTagMap(MapAction... actions) { + return makeTagMap(Arrays.asList(actions)); + } + + public static final TagMap makeTagMap(List actions) { + TagMap map = new TagMap(); + for ( MapAction action: actions ) { + action.apply(map); + } + return map; + } + + public static final TagMap test(TestCase test) { + List actions = test.actions(); + + Map hashMap = new HashMap<>(); + TagMap tagMap = new TagMap(); + + int actionIndex = 0; + try { + for ( actionIndex = 0; actionIndex < actions.size(); ++actionIndex ) { + MapAction action = actions.get(actionIndex); + + Object expected = action.apply(hashMap); + Object result = action.apply(tagMap); + + assertEquals(expected, result); + + action.verify(tagMap); + + assertMapEquals(hashMap, tagMap); + } + } catch ( Error e ) { + System.err.println(new TestCase(actions.subList(0, actionIndex + 1))); + + throw e; + } + return tagMap; + } + + public static final TestCase generateTest() { + return generateTest(ThreadLocalRandom.current().nextInt(MAX_NUM_ACTIONS)); + } + + public static final TestCase generateTest(int size) { + List actions = new ArrayList<>(size); + for ( int i = 0; i < size; ++i ) { + actions.add(randomAction()); + } + return new TestCase(actions); + } + + public static final MapAction randomAction() { + float actionSelector = ThreadLocalRandom.current().nextFloat(); + + if ( actionSelector > 0.5 ) { + // 50% puts + return put(randomKey(), randomValue()); + } else if ( actionSelector > 0.3 ) { + // 20% removes + return remove(randomKey()); + } else if ( actionSelector > 0.2 ) { + // 10% putAll TagMap + return putAllTagMap(randomKeysAndValues()); + } else if ( actionSelector > 0.02 ) { + // ~10% putAll HashMap + return putAll(randomKeysAndValues()); + } else { + return clear(); + } + } + + public static final MapAction put(String key, String value) { + return new Put(key, value); + } + + public static final MapAction putAll(String... keysAndValues) { + return new PutAll(keysAndValues); + } + + public static final MapAction putAllTagMap(String... keysAndValues) { + return new PutAllTagMap(keysAndValues); + } + + public static final MapAction clear() { + return Clear.INSTANCE; + } + + public static final MapAction remove(String key) { + return new Remove(key); + } + + static final void assertMapEquals(Map expected, TagMap actual) { + // checks entries in both directions to make sure there's full intersection + + for ( Map.Entry expectedEntry: expected.entrySet() ) { + TagMap.Entry actualEntry = actual.getEntry(expectedEntry.getKey()); + assertNotNull(actualEntry); + assertEquals(expectedEntry.getValue(), actualEntry.getValue()); + } + + for ( TagMap.Entry actualEntry: actual ) { + Object expectedValue = expected.get(actualEntry.tag()); + assertEquals(expectedValue, actualEntry.objectValue()); + } + } + + static final String randomKey() { + return "key-" + ThreadLocalRandom.current().nextInt(NUM_KEYS); + } + + static final String randomValue() { + return "values-" + ThreadLocalRandom.current().nextInt(); + } + + static final String[] randomKeysAndValues() { + int numEntries = ThreadLocalRandom.current().nextInt(NUM_KEYS); + + String[] keysAndValues = new String[numEntries << 1]; + for ( int i = 0; i < keysAndValues.length; i += 2 ) { + keysAndValues[i] = randomKey(); + keysAndValues[i + 1] = randomValue(); + } + return keysAndValues; + } + + static final String literal(String str) { + return "\"" + str + "\""; + } + + static final String literalVarArgs(String... strs) { + StringBuilder builder = new StringBuilder(); + for ( String str: strs ) { + if ( builder.length() != 0 ) builder.append(','); + builder.append(literal(str)); + } + return builder.toString(); + } + + static final Map mapOf(String... keysAndValues) { + HashMap map = new HashMap<>(keysAndValues.length >> 1); + for ( int i = 0; i < keysAndValues.length; i += 2 ) { + String key = keysAndValues[i]; + String value = keysAndValues[i + 1]; + + map.put(key, value); + } + return map; + } + + static final TagMap tagMapOf(String... keysAndValues) { + TagMap map = new TagMap(); + for ( int i = 0; i < keysAndValues.length; i += 2 ) { + String key = keysAndValues[i]; + String value = keysAndValues[i + 1]; + + map.set(key, value); + // map.check(); + } + return map; + } + + static final class TestCase { + final List actions; + + TestCase(MapAction... actions) { + this.actions = Arrays.asList(actions); + } + + TestCase(List actions) { + this.actions = actions; + } + + public final List actions() { + return this.actions; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for ( MapAction action: this.actions ) { + builder.append(action).append(',').append('\n'); + } + return builder.toString(); + } + } + + static abstract class MapAction { + public abstract Object apply(Map mapUnderTest); + + public abstract void verify(Map mapUnderTest); + + public abstract String toString(); + } + + static final class Put extends MapAction { + final String key; + final String value; + + Put(String key, String value) { + this.key = key; + this.value = value; + } + + @Override + public Object apply(Map mapUnderTest) { + return mapUnderTest.put(this.key, this.value); + } + + @Override + public void verify(Map mapUnderTest) { + assertEquals(this.value, mapUnderTest.get(this.key)); + } + + @Override + public String toString() { + return String.format("put(%s,%s)", literal(this.key), literal(this.value)); + } + } + + static final class PutAll extends MapAction { + final String[] keysAndValues; + final Map map; + + PutAll(String... keysAndValues) { + this.keysAndValues = keysAndValues; + this.map = mapOf(keysAndValues); + } + + @Override + public Object apply(Map mapUnderTest) { + mapUnderTest.putAll(this.map); + + return void.class; + } + + @Override + public void verify(Map mapUnderTest) { + for ( Map.Entry entry: this.map.entrySet() ) { + assertEquals(entry.getValue(), mapUnderTest.get(entry.getKey())); + } + } + + @Override + public String toString() { + return String.format("putAll(%s)", literalVarArgs(this.keysAndValues)); + } + } + + static final class PutAllTagMap extends MapAction { + final String[] keysAndValues; + final TagMap tagMap; + + PutAllTagMap(String... keysAndValues) { + this.keysAndValues = keysAndValues; + this.tagMap = tagMapOf(keysAndValues); + } + + @Override + public Object apply(Map mapUnderTest) { + mapUnderTest.putAll(this.tagMap); + + return void.class; + } + + @Override + public void verify(Map mapUnderTest) { + for ( TagMap.Entry entry: this.tagMap ) { + assertEquals(entry.objectValue(), mapUnderTest.get(entry.tag()), "key=" + entry.tag()); + } + } + + @Override + public String toString() { + return String.format("putAllTagMap(%s)", literalVarArgs(this.keysAndValues)); + } + } + + static final class Remove extends MapAction { + final String key; + + Remove(String key) { + this.key = key; + } + + @Override + public Object apply(Map mapUnderTest) { + return mapUnderTest.remove(this.key); + } + + @Override + public void verify(Map mapUnderTest) { + assertFalse(mapUnderTest.containsKey(this.key)); + } + + @Override + public String toString() { + return String.format("remove(%s)", literal(this.key)); + } + } + + static final class Clear extends MapAction { + static final Clear INSTANCE = new Clear(); + + private Clear() {} + + @Override + public Object apply(Map mapUnderTest) { + mapUnderTest.clear(); + + return void.class; + } + + @Override + public void verify(Map mapUnderTest) { + assertTrue(mapUnderTest.isEmpty()); + } + + @Override + public String toString() { + return "clear()"; + } + } +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java new file mode 100644 index 00000000000..4be1b76dcc9 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -0,0 +1,371 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.Test; + +public class TagMapTest { + // size is chosen to make sure to stress all types of collisions in the Map + static final int MANY_SIZE = 256; + + @Test + public void map_put() { + TagMap map = new TagMap(); + + Object prev = map.put("foo", "bar"); + assertNull(prev); + + assertEntry("foo", "bar", map); + + assertSize(1, map); + assertNotEmpty(map); + } + + @Test + public void clear() { + int size = randomSize(); + + TagMap map = createTagMap(size); + assertSize(size, map); + assertNotEmpty(map); + + map.clear(); + assertSize(0, map); + assertEmpty(map); + } + + @Test + public void map_put_replacement() { + TagMap map = new TagMap(); + Object prev1 = map.put("foo", "bar"); + assertNull(prev1); + + assertEntry("foo", "bar", map); + assertSize(1, map); + assertNotEmpty(map); + + Object prev2 = map.put("foo", "baz"); + assertEquals("bar", prev2); + + assertEntry("foo", "baz", map); + } + + @Test + public void map_remove() { + TagMap map = new TagMap(); + + Object prev1 = map.remove("foo"); + assertNull(prev1); + + map.put("foo", "bar"); + assertEntry("foo", "bar", map); + assertSize(1, map); + assertNotEmpty(map); + + Object prev2 = map.remove("foo"); + assertEquals("bar", prev2); + assertSize(0, map); + assertEmpty(map); + } + + @Test + public void freeze() { + TagMap map = new TagMap(); + map.put("foo", "bar"); + + assertEntry("foo", "bar", map); + + map.freeze(); + + assertFrozen(() -> { + map.remove("foo"); + }); + + assertEntry("foo", "bar", map); + } + + @Test + public void emptyMap() { + TagMap map = TagMap.EMPTY; + + assertSize(0, map); + assertEmpty(map); + + assertFrozen(map); + } + + + @Test + public void putMany() { + int size = randomSize(); + TagMap map = createTagMap(size); + + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), map); + } + + assertNotEmpty(map); + assertSize(size, map); + } + + @Test + public void cloneMany() { + int size = randomSize(); + TagMap orig = createTagMap(size); + + TagMap copy = orig.copy(); + orig.clear(); // doing this to make sure that copied isn't modified + + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), copy); + } + } + + @Test + public void replaceALot() { + int size = randomSize(); + TagMap map = createTagMap(size); + + for ( int i = 0; i < size; ++i ) { + int index = ThreadLocalRandom.current().nextInt(size); + + map.put(key(index), altValue(index)); + assertEquals(altValue(index), map.get(key(index))); + } + } + + @Test + public void shareEntry() { + TagMap orig = new TagMap(); + orig.set("foo", "bar"); + + TagMap dest = new TagMap(); + dest.putEntry(orig.getEntry("foo")); + + assertSame(orig.getEntry("foo"), dest.getEntry("foo")); + } + + @Test + public void putAll() { + int size = 67; + TagMap orig = createTagMap(size); + + TagMap dest = new TagMap(); + for ( int i = size - 1; i >= 0 ; --i ) { + dest.set(key(i), altValue(i)); + } + + // This should clobber all the values in dest + dest.putAll(orig); + + // assertSize(size, dest); + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), dest); + } + } + + @Test + public void removeMany() { + int size = randomSize(); + TagMap map = createTagMap(size); + + for ( int i = 0; i < size; ++i ) { + assertEntry(key(i), value(i), map); + } + + assertNotEmpty(map); + assertSize(size, map); + + for ( int i = 0; i < size; ++i ) { + Object removedValue = map.remove(key(i)); + assertEquals(value(i), removedValue); + + // not doing exhaustive size checks + assertEquals(size - i - 1, map.computeSize()); + } + + assertEmpty(map); + } + + @Test + public void iterator() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(); + for ( TagMap.Entry entry: map ) { + // makes sure that each key is visited once and only once + assertTrue(keys.add(entry.tag())); + } + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + @Test + public void forEachConsumer() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(size); + map.forEach((entry) -> keys.add(entry.tag())); + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + @Test + public void forEachBiConsumer() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(size); + map.forEach(keys, (k, entry) -> k.add(entry.tag())); + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + @Test + public void forEachTriConsumer() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set keys = new HashSet<>(size); + map.forEach(keys, "hi", (k, msg, entry) -> keys.add(entry.tag())); + + for ( int i = 0; i < size; ++i ) { + // make sure the key was present + assertTrue(keys.remove(key(i))); + } + + // no extraneous keys + assertTrue(keys.isEmpty()); + } + + static final int randomSize() { + return ThreadLocalRandom.current().nextInt(MANY_SIZE); + } + + static final TagMap createTagMap() { + return createTagMap(randomSize()); + } + + static final TagMap createTagMap(int size) { + TagMap map = new TagMap(); + for ( int i = 0; i < size; ++i ) { + map.set(key(i), value(i)); + } + return map; + } + + static final String key(int i) { + return "key-" + i; + } + + static final String value(int i) { + return "value-" + i; + } + + static final String altValue(int i) { + return "alt-value-" + i; + } + + static final int count(Iterable iterable) { + return count(iterable.iterator()); + } + + static final int count(Iterator iter) { + int count; + for ( count = 0; iter.hasNext(); ++count ) { + iter.next(); + } + return count; + } + + static final void assertEntry(String key, String value, TagMap map) { + TagMap.Entry entry = map.getEntry(key); + assertNotNull(entry); + + assertEquals(key, entry.tag()); + assertEquals(key, entry.getKey()); + + assertEquals(value, entry.objectValue()); + assertTrue(entry.isObject()); + assertEquals(value, entry.getValue()); + + assertEquals(value, entry.stringValue()); + + assertTrue(map.containsKey(key)); + assertTrue(map.keySet().contains(key)); + + assertTrue(map.containsValue(value)); + assertTrue(map.values().contains(value)); + } + + static final void assertSize(int size, TagMap map) { + assertEquals(size, map.computeSize()); + assertEquals(size, map.size()); + + assertEquals(size, count(map)); + assertEquals(size, map.keySet().size()); + assertEquals(size, map.values().size()); + assertEquals(size, count(map.keySet())); + assertEquals(size, count(map.values())); + } + + static final void assertNotEmpty(TagMap map) { + assertFalse(map.checkIfEmpty()); + assertFalse(map.isEmpty()); + } + + static final void assertEmpty(TagMap map) { + assertTrue(map.checkIfEmpty()); + assertTrue(map.isEmpty()); + } + + static final void assertFrozen(TagMap map) { + IllegalStateException ex = null; + try { + map.put("foo", "bar"); + } catch ( IllegalStateException e ) { + ex = e; + } + assertNotNull(ex); + } + + static final void assertFrozen(Runnable runnable) { + IllegalStateException ex = null; + try { + runnable.run(); + } catch ( IllegalStateException e ) { + ex = e; + } + assertNotNull(ex); + } +} From ed3b5a80070227aed1f697b31f7f2624392a5c05 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 11:50:37 -0400 Subject: [PATCH 33/84] spotless --- .../domain/AbstractTestSession.java | 1 - .../trace/civisibility/domain/TestImpl.java | 5 +- .../DefaultExceptionDebuggerTest.java | 1 - .../java/datadog/trace/core/CoreTracer.java | 98 +- .../datadog/trace/core/DDSpanContext.java | 60 +- .../java/datadog/trace/core/Metadata.java | 4 +- .../trace/core/propagation/B3HttpCodec.java | 1 - .../core/propagation/ContextInterpreter.java | 28 +- .../core/propagation/ExtractedContext.java | 15 +- .../core/taginterceptor/TagInterceptor.java | 28 +- .../core/tagprocessor/BaseServiceAdder.java | 3 +- .../tagprocessor/PayloadTagsProcessor.java | 2 +- .../tagprocessor/PeerServiceCalculator.java | 1 - .../core/tagprocessor/PostProcessorChain.java | 5 +- .../core/tagprocessor/QueryObfuscator.java | 4 +- .../tagprocessor/RemoteHostnameAdder.java | 1 - .../tagprocessor/SpanPointersProcessor.java | 3 +- .../core/tagprocessor/TagsPostProcessor.java | 14 +- .../PostProcessorChainTest.groovy | 2 +- .../main/java/datadog/trace/api/Config.java | 4 +- .../main/java/datadog/trace/api/TagMap.java | 1460 ++++++++--------- .../datadog/trace/api/gateway/IGSpanInfo.java | 3 +- .../trace/api/naming/NamingSchema.java | 1 - .../api/naming/v0/PeerServiceNamingV0.java | 5 +- .../api/naming/v1/PeerServiceNamingV1.java | 3 +- .../instrumentation/api/AgentSpan.java | 6 +- .../instrumentation/api/ExtractedSpan.java | 1 - .../instrumentation/api/NoopSpan.java | 5 +- .../instrumentation/api/TagContext.java | 21 +- .../datadog/trace/api/TagMapBuilderTest.java | 54 +- .../datadog/trace/api/TagMapEntryTest.java | 154 +- .../datadog/trace/api/TagMapFuzzTest.java | 1039 ++++++++++-- .../java/datadog/trace/api/TagMapTest.java | 199 ++- 33 files changed, 1993 insertions(+), 1238 deletions(-) diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java index a63f2e8785e..fdc4cdbd7ba 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/AbstractTestSession.java @@ -6,7 +6,6 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; import datadog.trace.api.IdGenerationStrategy; -import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.telemetry.CiVisibilityCountMetric; import datadog.trace.api.civisibility.telemetry.CiVisibilityMetricCollector; diff --git a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java index 01c996254d3..900ecc8c9c9 100644 --- a/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java +++ b/dd-java-agent/agent-ci-visibility/src/main/java/datadog/trace/civisibility/domain/TestImpl.java @@ -6,7 +6,6 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTraceId; -import datadog.trace.api.TagMap; import datadog.trace.api.civisibility.CIConstants; import datadog.trace.api.civisibility.DDTest; import datadog.trace.api.civisibility.InstrumentationTestBridge; @@ -45,7 +44,6 @@ import datadog.trace.civisibility.test.ExecutionResults; import java.lang.reflect.Method; import java.util.Collection; -import java.util.Collections; import java.util.function.Consumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -102,8 +100,7 @@ public TestImpl( this.context = new TestContextImpl(coverageStore); - AgentSpanContext traceContext = - new TagContext(CIConstants.CIAPP_TEST_ORIGIN, null); + AgentSpanContext traceContext = new TagContext(CIConstants.CIAPP_TEST_ORIGIN, null); AgentTracer.SpanBuilder spanBuilder = AgentTracer.get() .buildSpan(CI_VISIBILITY_INSTRUMENTATION_NAME, testDecorator.component() + ".test") diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java index bbc2e3f33c1..a00d72095db 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java @@ -42,7 +42,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; 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 024f83d6f5b..097ea534104 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 @@ -107,7 +107,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -189,12 +188,14 @@ public static CoreTracerBuilder builder() { /** A set of tags that are added only to the application's root span */ private final TagMap localRootSpanTags; + private final boolean localRootSpanTagsNeedIntercept; /** A set of tags that are added to every span */ private final TagMap defaultSpanTags; + private boolean defaultSpanTagsNeedsIntercept; - + /** number of spans in a pending trace before they get flushed */ private final int partialFlushMinSpans; @@ -376,7 +377,7 @@ public CoreTracerBuilder localRootSpanTags(Map localRootSpanTags) { this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); return this; } - + public CoreTracerBuilder localRootSpanTags(TagMap tagMap) { this.localRootSpanTags = tagMap.immutableCopy(); return this; @@ -386,7 +387,7 @@ public CoreTracerBuilder defaultSpanTags(Map defaultSpanTags) { this.defaultSpanTags = TagMap.fromMapImmutable(defaultSpanTags); return this; } - + public CoreTracerBuilder defaultSpanTags(TagMap defaultSpanTags) { this.defaultSpanTags = defaultSpanTags.immutableCopy(); return this; @@ -537,7 +538,7 @@ public CoreTracer build() { flushOnClose); } } - + @Deprecated private CoreTracer( final Config config, @@ -566,33 +567,33 @@ private CoreTracer( final boolean pollForTracingConfiguration, final boolean injectBaggageAsTags, final boolean flushOnClose) { - this( - config, - serviceName, - sharedCommunicationObjects, - writer, - idGenerationStrategy, - sampler, - singleSpanSampler, - injector, - extractor, - TagMap.fromMap(localRootSpanTags), - defaultSpanTags, - serviceNameMappings, - taggedHeaders, - baggageMapping, - partialFlushMinSpans, - statsDClient, - tagInterceptor, - strictTraceWrites, - instrumentationGateway, - timeSource, - dataStreamsMonitoring, - profilingContextIntegration, - pollForTracerFlareRequests, - pollForTracingConfiguration, - injectBaggageAsTags, - flushOnClose); + this( + config, + serviceName, + sharedCommunicationObjects, + writer, + idGenerationStrategy, + sampler, + singleSpanSampler, + injector, + extractor, + TagMap.fromMap(localRootSpanTags), + defaultSpanTags, + serviceNameMappings, + taggedHeaders, + baggageMapping, + partialFlushMinSpans, + statsDClient, + tagInterceptor, + strictTraceWrites, + instrumentationGateway, + timeSource, + dataStreamsMonitoring, + profilingContextIntegration, + pollForTracerFlareRequests, + pollForTracingConfiguration, + injectBaggageAsTags, + flushOnClose); } // These field names must be stable to ensure the builder api is stable. @@ -859,8 +860,9 @@ private CoreTracer( } else { this.localRootSpanTags = TagMap.fromMapImmutable(localRootSpanTags); } - - this.localRootSpanTagsNeedIntercept = this.tagInterceptor.needsIntercept(this.localRootSpanTags); + + this.localRootSpanTagsNeedIntercept = + this.tagInterceptor.needsIntercept(this.localRootSpanTags); } /** Used by AgentTestRunner to inject configuration into the test tracer. */ @@ -1381,10 +1383,9 @@ public static class CoreSpanBuilder implements AgentTracer.SpanBuilder { private long spanId; CoreSpanBuilder( - final CoreTracer tracer, + final CoreTracer tracer, final String instrumentationName, - final CharSequence operationName) - { + final CharSequence operationName) { this.instrumentationName = instrumentationName; this.operationName = operationName; this.tracer = tracer; @@ -1502,8 +1503,8 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { } TagMap.Builder tagBuilder = this.tagBuilder; if (tagBuilder == null) { - // Insertion order is important, so using TagBuilder which builds up a set - // of Entry modifications in order + // Insertion order is important, so using TagBuilder which builds up a set + // of Entry modifications in order this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { @@ -1562,7 +1563,7 @@ private DDSpanContext buildSpanContext() { final int samplingPriority; final CharSequence origin; final TagMap coreTags; - final boolean coreTagsNeedsIntercept; + final boolean coreTagsNeedsIntercept; final TagMap rootSpanTags; final boolean rootSpanTagsNeedsIntercept; final DDSpanContext context; @@ -1752,12 +1753,12 @@ private DDSpanContext buildSpanContext() { boolean mergedTracerTagsNeedsIntercept = traceConfig.mergedTracerTagsNeedsIntercept; final int tagsSize = 0; -// final int tagsSize = -// mergedTracerTags.computeSize() -// + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) -// + (null == coreTags ? 0 : coreTags.size()) -// + (null == rootSpanTags ? 0 : rootSpanTags.size()) -// + (null == contextualTags ? 0 : contextualTags.size()); + // final int tagsSize = + // mergedTracerTags.computeSize() + // + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) + // + (null == coreTags ? 0 : coreTags.size()) + // + (null == rootSpanTags ? 0 : rootSpanTags.size()) + // + (null == contextualTags ? 0 : contextualTags.size()); if (builderRequestContextDataAppSec != null) { requestContextDataAppSec = builderRequestContextDataAppSec; @@ -1834,7 +1835,7 @@ protected class ConfigSnapshot extends DynamicConfig.Snapshot { protected ConfigSnapshot( DynamicConfig.Builder builder, ConfigSnapshot oldSnapshot) { super(builder, oldSnapshot); - + if (null == oldSnapshot) { sampler = CoreTracer.this.initialSampler; } else if (Objects.equals(getTraceSampleRate(), oldSnapshot.getTraceSampleRate()) @@ -1852,7 +1853,8 @@ protected ConfigSnapshot( mergedTracerTagsNeedsIntercept = oldSnapshot.mergedTracerTagsNeedsIntercept; } else { mergedTracerTags = withTracerTags(getTracingTags(), CoreTracer.this.initialConfig, this); - mergedTracerTagsNeedsIntercept = CoreTracer.this.tagInterceptor.needsIntercept(mergedTracerTags); + mergedTracerTagsNeedsIntercept = + CoreTracer.this.tagInterceptor.needsIntercept(mergedTracerTags); } } } 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 3349205d444..4efee4c0e6a 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 @@ -8,7 +8,6 @@ import datadog.trace.api.DDTraceId; import datadog.trace.api.Functions; import datadog.trace.api.TagMap; - import datadog.trace.api.cache.DDCache; import datadog.trace.api.cache.DDCaches; import datadog.trace.api.config.TracerConfig; @@ -343,7 +342,7 @@ public DDSpanContext( this.pathwayContext = pathwayContext; this.unsafeTags = new TagMap(); - + // must set this before setting the service and resource names below this.profilingContextIntegration = profilingContextIntegration; // as fast as we can try to make this operation, we still might need to activate/deactivate @@ -749,42 +748,45 @@ public void setTag(final String tag, final Object value) { } } } - + void setAllTags(final TagMap map, boolean needsIntercept) { - if ( map == null ) { + if (map == null) { return; } - + synchronized (unsafeTags) { - if ( needsIntercept ) { - map.forEach(this, traceCollector.getTracer().getTagInterceptor(), (ctx, tagInterceptor, tagEntry) -> { - String tag = tagEntry.tag(); - Object value = tagEntry.objectValue(); - - if (!tagInterceptor.interceptTag(ctx, tag, value)) { - ctx.unsafeTags.putEntry(tagEntry); - } - }); + if (needsIntercept) { + map.forEach( + this, + traceCollector.getTracer().getTagInterceptor(), + (ctx, tagInterceptor, tagEntry) -> { + String tag = tagEntry.tag(); + Object value = tagEntry.objectValue(); + + if (!tagInterceptor.interceptTag(ctx, tag, value)) { + ctx.unsafeTags.putEntry(tagEntry); + } + }); } else { - unsafeTags.putAll(map); + unsafeTags.putAll(map); } } } - + void setAllTags(final TagMap.Builder builder) { - if ( builder == null ) { + if (builder == null) { return; } - + TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { for (final TagMap.Entry tagEntry : builder) { - if ( tagEntry.isRemoval() ) { + if (tagEntry.isRemoval()) { unsafeTags.removeEntry(tagEntry.tag()); } else { - String tag = tagEntry.tag(); + String tag = tagEntry.tag(); Object value = tagEntry.objectValue(); - + if (!tagInterceptor.interceptTag(this, tag, value)) { unsafeTags.putEntry(tagEntry); } @@ -794,11 +796,11 @@ void setAllTags(final TagMap.Builder builder) { } void setAllTags(final Map map) { - if ( map == null ) { - return; - } else if ( map instanceof TagMap ) { - setAllTags((TagMap)map); - } else if ( !map.isEmpty() ) { + if (map == null) { + return; + } else if (map instanceof TagMap) { + setAllTags((TagMap) map); + } else if (!map.isEmpty()) { TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { for (final Map.Entry tag : map.entrySet()) { @@ -807,7 +809,7 @@ void setAllTags(final Map map) { } } } - } + } } void unsafeSetTag(final String tag, final Object value) { @@ -848,7 +850,7 @@ public Object unsafeGetTag(final String tag) { public TagMap getTags() { synchronized (unsafeTags) { TagMap tags = unsafeTags.copy(); - + tags.put(DDTags.THREAD_ID, threadId); // maintain previously observable type of the thread name :| tags.put(DDTags.THREAD_NAME, threadName.toString()); @@ -896,7 +898,7 @@ public void processTagsAndBaggage( synchronized (unsafeTags) { // Tags TagsPostProcessorFactory.instance().processTags(unsafeTags, this, links); - + String linksTag = DDSpanLink.toTag(links); if (linksTag != null) { unsafeTags.put(SPAN_LINKS, linksTag); 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 08a1f46ca9f..e24aa8286e7 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,11 +2,9 @@ import static datadog.trace.api.sampling.PrioritySampling.UNSET; -import java.util.HashMap; -import java.util.Map; - import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; +import java.util.Map; public final class Metadata { private final long threadId; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java index f13cabf2ec6..c9af7dec2a2 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java @@ -16,7 +16,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.TreeMap; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java index b8e693a57f9..972c4b6d43e 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java @@ -15,10 +15,6 @@ import static datadog.trace.core.propagation.HttpCodec.X_FORWARDED_PROTO_KEY; import static datadog.trace.core.propagation.HttpCodec.X_REAL_IP_KEY; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; - import datadog.trace.api.Config; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; @@ -32,6 +28,9 @@ import datadog.trace.bootstrap.ActiveSubsystems; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.TagContext; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; /** * When adding new context fields to the ContextInterpreter class remember to clear them in the @@ -78,12 +77,12 @@ protected ContextInterpreter(Config config) { this.propagationTagsFactory = PropagationTags.factory(config); this.requestHeaderTagsCommaAllowed = config.isRequestHeaderTagsCommaAllowed(); } - + final TagMap.Builder tagBuilder() { - if ( tagBuilder == null ) { - tagBuilder = TagMap.builder(); - } - return tagBuilder; + if (tagBuilder == null) { + tagBuilder = TagMap.builder(); + } + return tagBuilder; } /** @@ -198,10 +197,11 @@ protected final boolean handleTags(String key, String value) { final String lowerCaseKey = toLowerCase(key); final String mappedKey = headerTags.get(lowerCaseKey); if (null != mappedKey) { - tagBuilder().put( - mappedKey, - HttpCodec.decode( - requestHeaderTagsCommaAllowed ? value : HttpCodec.firstHeaderValue(value))); + tagBuilder() + .put( + mappedKey, + HttpCodec.decode( + requestHeaderTagsCommaAllowed ? value : HttpCodec.firstHeaderValue(value))); return true; } return false; @@ -230,7 +230,7 @@ public ContextInterpreter reset(TraceConfig traceConfig) { samplingPriority = PrioritySampling.UNSET; origin = null; endToEndStartTime = 0; - if ( tagBuilder != null ) tagBuilder.reset(); + if (tagBuilder != null) tagBuilder.reset(); baggage = Collections.emptyMap(); valid = true; fullContext = true; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java index 07e594adc47..af503e6a6ed 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ExtractedContext.java @@ -64,7 +64,7 @@ public ExtractedContext( this.endToEndStartTime = endToEndStartTime; this.propagationTags = propagationTags; } - + /* * DQH - kept for testing purposes only */ @@ -81,7 +81,18 @@ public ExtractedContext( final PropagationTags propagationTags, final TraceConfig traceConfig, final TracePropagationStyle propagationStyle) { - this(traceId, spanId, samplingPriority, origin, endToEndStartTime, baggage, tags == null ? null : TagMap.fromMap(tags), httpHeaders, propagationTags, traceConfig, propagationStyle); + this( + traceId, + spanId, + samplingPriority, + origin, + endToEndStartTime, + baggage, + tags == null ? null : TagMap.fromMap(tags), + httpHeaders, + propagationTags, + traceConfig, + propagationStyle); } @Override diff --git a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java index b0f10a5f3bc..3da653c5398 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/taginterceptor/TagInterceptor.java @@ -83,21 +83,21 @@ public TagInterceptor( shouldSetUrlResourceAsName = ruleFlags.isEnabled(URL_AS_RESOURCE_NAME); this.jeeSplitByDeployment = jeeSplitByDeployment; } - + public boolean needsIntercept(TagMap map) { - for ( TagMap.Entry entry: map ) { - if ( needsIntercept(entry.tag()) ) return true; - } - return false; + for (TagMap.Entry entry : map) { + if (needsIntercept(entry.tag())) return true; + } + return false; } - + public boolean needsIntercept(Map map) { - for ( String tag: map.keySet() ) { - if ( needsIntercept(tag) ) return true; - } - return false; + for (String tag : map.keySet()) { + if (needsIntercept(tag)) return true; + } + return false; } - + public boolean needsIntercept(String tag) { switch (tag) { case DDTags.RESOURCE_NAME: @@ -120,10 +120,10 @@ public boolean needsIntercept(String tag) { case HTTP_URL: case ORIGIN_KEY: case MEASURED: - return true; - + return true; + default: - return splitServiceTags.contains(tag); + return splitServiceTags.contains(tag); } } 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 5d43edd8046..4a2f2d5377f 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 @@ -5,7 +5,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString; import datadog.trace.core.DDSpanContext; - import java.util.List; import javax.annotation.Nullable; @@ -24,6 +23,6 @@ public void processTags( && !ddService.toString().equalsIgnoreCase(spanContext.getServiceName())) { unsafeTags.put(DDTags.BASE_SERVICE, ddService); unsafeTags.remove("version"); - } + } } } 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 3a63ae7d390..3d413d98b5f 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 @@ -82,7 +82,7 @@ public void processTags( if (unsafeTags.remove(tagPrefix) != null) { spanMaxTags -= 1; } - + PayloadTagsData payloadTagsData = (PayloadTagsData) tagValue; PayloadTagsCollector payloadTagsCollector = new PayloadTagsCollector(maxDepth, spanMaxTags, redactionRules, tagPrefix, unsafeTags); 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 5f979ec8f04..9a4e9377cb9 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 @@ -8,7 +8,6 @@ import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.bootstrap.instrumentation.api.Tags; import datadog.trace.core.DDSpanContext; - import java.util.List; import java.util.Map; import javax.annotation.Nonnull; 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 5bef7746540..77374d742cb 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 @@ -3,9 +3,7 @@ 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; @@ -17,7 +15,8 @@ public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) { } @Override - public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { for (final TagsPostProcessor tagsPostProcessor : chain) { tagsPostProcessor.processTags(unsafeTags, spanContext, spanLinks); } 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 19fa70cc52f..37bbd470596 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 @@ -10,7 +10,6 @@ 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; @@ -59,7 +58,8 @@ private String obfuscate(String query) { } @Override - public void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { Object query = unsafeTags.getObject(DDTags.HTTP_QUERY); if (query instanceof CharSequence) { query = obfuscate(query.toString()); 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 953e8a67387..7bd45cd2c92 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 @@ -4,7 +4,6 @@ import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink; import datadog.trace.core.DDSpanContext; - import java.util.List; import java.util.function.Supplier; 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 abb01123e39..eb88235fe65 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 @@ -39,6 +39,7 @@ public class SpanPointersProcessor extends TagsPostProcessor { @Override public void processTags( TagMap 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) { spanLinks.add(s3Link); @@ -48,8 +49,6 @@ 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 4050e9baf30..d0acedb40ee 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 @@ -3,23 +3,19 @@ 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 + * 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; + Map unsafeTags, DDSpanContext context, List links) { + TagMap map = TagMap.fromMap(unsafeTags); + this.processTags(map, context, links); + return map; } public abstract void processTags( 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 cd82cc1a2a6..8961c8d41f8 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 @@ -46,7 +46,7 @@ class PostProcessorChainTest extends DDSpecification { void processTags(TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { if (unsafeTags.containsKey("test")) { unsafeTags.put("found", "true") - } + } } } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index a6ba7c24222..1a4a3cc0f6d 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3609,10 +3609,10 @@ public boolean isJdkSocketEnabled() { /** @return A map of tags to be applied only to the local application root span. */ public TagMap getLocalRootSpanTags() { final Map runtimeTags = getRuntimeTags(); - + final TagMap result = new TagMap(); result.putAll(runtimeTags); - + result.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); result.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); result.put(DDTags.PROFILING_ENABLED, isProfilingEnabled() ? 1 : 0); diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index f0839e1aacc..e988106f298 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1,5 +1,6 @@ package datadog.trace.api; +import datadog.trace.api.function.TriConsumer; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; @@ -13,177 +14,167 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import datadog.trace.api.function.TriConsumer; - /** - * A super simple hash map designed for... - * - fast copy from one map to another - * - compatibility with Builder idioms - * - building small maps as fast as possible - * - storing primitives without boxing - * - minimal memory footprint - * - * This is mainly accomplished by using immutable entry objects that can - * reference an object or a primitive. By using immutable entries, - * the entry objects can be shared between builders & maps freely. - * - * This map lacks some features of a regular java.util.Map... - * - Entry object mutation - * - size tracking - falls back to computeSize - * - manipulating Map through the entrySet() or values() - * - * Also lacks features designed for handling large maps... - * - bucket array expansion - * - adaptive collision + * A super simple hash map designed for... - fast copy from one map to another - compatibility with + * Builder idioms - building small maps as fast as possible - storing primitives without boxing - + * minimal memory footprint + * + *

This is mainly accomplished by using immutable entry objects that can reference an object or a + * primitive. By using immutable entries, the entry objects can be shared between builders & maps + * freely. + * + *

This map lacks some features of a regular java.util.Map... - Entry object mutation - size + * tracking - falls back to computeSize - manipulating Map through the entrySet() or values() + * + *

Also lacks features designed for handling large maps... - bucket array expansion - adaptive + * collision */ public final class TagMap implements Map, Iterable { public static final TagMap EMPTY = createEmpty(); - + static final TagMap createEmpty() { return new TagMap().freeze(); } - + public static final Builder builder() { return new Builder(); } - + public static final Builder builder(int size) { return new Builder(size); } - + public static final TagMap fromMap(Map map) { TagMap tagMap = new TagMap(); tagMap.putAll(map); return tagMap; } - + public static final TagMap fromMapImmutable(Map map) { - if ( map.isEmpty() ) { + if (map.isEmpty()) { return TagMap.EMPTY; } else { return fromMap(map).freeze(); } } - + private final Object[] buckets; private boolean frozen; - + public TagMap() { // needs to be a power of 2 for bucket masking calculation to work as intended this.buckets = new Object[1 << 4]; this.frozen = false; } - - /** - * Used for inexpensive immutable - */ + + /** Used for inexpensive immutable */ private TagMap(Object[] buckets) { this.buckets = buckets; this.frozen = true; } - + @Deprecated @Override public final int size() { return this.computeSize(); } - + /** * Computes the size of the TagMap - * - * computeSize is fast but is an O(n) operation. + * + *

computeSize is fast but is an O(n) operation. */ public final int computeSize() { Object[] thisBuckets = this.buckets; - + int size = 0; - for ( int i = 0; i < thisBuckets.length; ++i ) { + for (int i = 0; i < thisBuckets.length; ++i) { Object curBucket = thisBuckets[i]; - - if ( curBucket instanceof Entry ) { + + if (curBucket instanceof Entry) { size += 1; - } else if ( curBucket instanceof BucketGroup ) { - BucketGroup curGroup = (BucketGroup)curBucket; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; size += curGroup.sizeInChain(); } } return size; } - + @Deprecated @Override public boolean isEmpty() { return this.checkIfEmpty(); } - + public final boolean checkIfEmpty() { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object curBucket = thisBuckets[i]; - - if ( curBucket instanceof Entry ) { + + if (curBucket instanceof Entry) { return false; - } else if ( curBucket instanceof BucketGroup ) { - BucketGroup curGroup = (BucketGroup)curBucket; - if ( !curGroup.isEmptyChain() ) return false; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; + if (!curGroup.isEmptyChain()) return false; } } - + return true; } - + @Override public boolean containsKey(Object key) { - if ( !(key instanceof String) ) return false; - - return (this.getEntry((String)key) != null); + if (!(key instanceof String)) return false; + + return (this.getEntry((String) key) != null); } - + @Override public boolean containsValue(Object value) { - for ( Entry entry : this ) { - if ( entry.objectValue().equals(value) ) return true; + for (Entry entry : this) { + if (entry.objectValue().equals(value)) return true; } return false; } - + @Deprecated @Override public Set keySet() { return new Keys(this); } - + @Deprecated @Override public Collection values() { return new Values(this); } - + @Deprecated @Override public Set> entrySet() { return new Entries(this); } - + @Deprecated @Override public final Object get(Object tag) { - if ( !(tag instanceof String) ) return null; - - return this.getObject((String)tag); + if (!(tag instanceof String)) return null; + + return this.getObject((String) tag); } - + public final Object getObject(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.objectValue(); } - + public final String getString(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.stringValue(); } - + public final boolean getBoolean(String tag) { Entry entry = this.getEntry(tag); return entry == null ? false : entry.booleanValue(); @@ -193,22 +184,22 @@ public final int getInt(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0 : entry.intValue(); } - + public final long getLong(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0L : entry.longValue(); } - + public final float getFloat(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0F : entry.floatValue(); } - + public final double getDouble(String tag) { Entry entry = this.getEntry(tag); return entry == null ? 0D : entry.doubleValue(); } - + public final Entry getEntry(String tag) { Object[] thisBuckets = this.buckets; @@ -216,180 +207,181 @@ public final Entry getEntry(String tag) { int bucketIndex = hash & (thisBuckets.length - 1); Object bucket = thisBuckets[bucketIndex]; - if ( bucket == null ) { + if (bucket == null) { return null; - } else if ( bucket instanceof Entry ) { - Entry tagEntry = (Entry)bucket; - if ( tagEntry.matches(tag) ) return tagEntry; - } else if ( bucket instanceof BucketGroup ) { - BucketGroup lastGroup = (BucketGroup)bucket; - + } else if (bucket instanceof Entry) { + Entry tagEntry = (Entry) bucket; + if (tagEntry.matches(tag)) return tagEntry; + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; + Entry tagEntry = lastGroup.findInChain(hash, tag); - if ( tagEntry != null ) return tagEntry; + if (tagEntry != null) return tagEntry; } return null; } - + @Deprecated public final Object put(String tag, Object value) { TagMap.Entry entry = this.putEntry(Entry.newAnyEntry(tag, value)); return entry == null ? null : entry.objectValue(); } - + public final Entry set(String tag, Object value) { return this.putEntry(Entry.newAnyEntry(tag, value)); } - + public final Entry set(String tag, CharSequence value) { return this.putEntry(Entry.newObjectEntry(tag, value)); } - + public final Entry set(String tag, boolean value) { return this.putEntry(Entry.newBooleanEntry(tag, value)); - } - + } + public final Entry set(String tag, int value) { return this.putEntry(Entry.newIntEntry(tag, value)); } - + public final Entry set(String tag, long value) { return this.putEntry(Entry.newLongEntry(tag, value)); } - + public final Entry set(String tag, float value) { return this.putEntry(Entry.newFloatEntry(tag, value)); } - + public final Entry set(String tag, double value) { return this.putEntry(Entry.newDoubleEntry(tag, value)); } - + public final Entry putEntry(Entry newEntry) { this.checkWriteAccess(); - + Object[] thisBuckets = this.buckets; int newHash = newEntry.hash(); int bucketIndex = newHash & (thisBuckets.length - 1); - + Object bucket = thisBuckets[bucketIndex]; - if ( bucket == null ) { + if (bucket == null) { thisBuckets[bucketIndex] = newEntry; - + return null; - } else if ( bucket instanceof Entry ) { - Entry existingEntry = (Entry)bucket; - if ( existingEntry.matches(newEntry.tag) ) { + } else if (bucket instanceof Entry) { + Entry existingEntry = (Entry) bucket; + if (existingEntry.matches(newEntry.tag)) { thisBuckets[bucketIndex] = newEntry; - + return existingEntry; } else { - thisBuckets[bucketIndex] = new BucketGroup( - existingEntry.hash(), existingEntry, - newHash, newEntry); - + thisBuckets[bucketIndex] = + new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry); + return null; } - } else if ( bucket instanceof BucketGroup ){ - BucketGroup lastGroup = (BucketGroup)bucket; - + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag); - if ( containingGroup != null ) { + if (containingGroup != null) { return containingGroup._replace(newHash, newEntry); } - - if ( !lastGroup.insertInChain(newHash, newEntry) ) { + + if (!lastGroup.insertInChain(newHash, newEntry)) { thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup); } return null; } return null; } - + public final void putAll(Iterable entries) { this.checkWriteAccess(); - - for ( Entry tagEntry: entries ) { - if ( tagEntry.isRemoval() ) { + + for (Entry tagEntry : entries) { + if (tagEntry.isRemoval()) { this.remove(tagEntry.tag); } else { this.putEntry(tagEntry); } } } - + public final void putAll(TagMap.Builder builder) { putAll(builder.entries, builder.nextPos); } - + private final void putAll(Entry[] tagEntries, int size) { - for ( int i = 0; i < size && i < tagEntries.length; ++i ) { + for (int i = 0; i < size && i < tagEntries.length; ++i) { Entry tagEntry = tagEntries[i]; - - if ( tagEntry.isRemoval() ) { + + if (tagEntry.isRemoval()) { this.remove(tagEntry.tag); } else { this.putEntry(tagEntry); } } } - + public final void putAll(TagMap that) { this.checkWriteAccess(); - + Object[] thisBuckets = this.buckets; Object[] thatBuckets = that.buckets; // Since TagMap-s don't support expansion, buckets are perfectly aligned - for ( int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i ) { + for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { Object thatBucket = thatBuckets[i]; // nothing in the other hash, just skip this bucket - if ( thatBucket == null ) continue; + if (thatBucket == null) continue; Object thisBucket = thisBuckets[i]; - if ( thisBucket == null ) { + if (thisBucket == null) { // This bucket is null, easy case // Either copy over the sole entry or clone the BucketGroup chain - if ( thatBucket instanceof Entry ) { + if (thatBucket instanceof Entry) { thisBuckets[i] = thatBucket; - } else if ( thatBucket instanceof BucketGroup ) { - BucketGroup thatGroup = (BucketGroup)thatBucket; - + } else if (thatBucket instanceof BucketGroup) { + BucketGroup thatGroup = (BucketGroup) thatBucket; + thisBuckets[i] = thatGroup.cloneChain(); } - } else if ( thisBucket instanceof Entry ) { + } else if (thisBucket instanceof Entry) { // This bucket is a single entry, medium complexity case // If other side is an Entry - just merge the entries into a bucket - // If other side is a BucketGroup - then clone the group and insert the entry normally into the cloned group - - Entry thisEntry = (Entry)thisBucket; + // If other side is a BucketGroup - then clone the group and insert the entry normally into + // the cloned group + + Entry thisEntry = (Entry) thisBucket; int thisHash = thisEntry.hash(); - - if ( thatBucket instanceof Entry ) { - Entry thatEntry = (Entry)thatBucket; + + if (thatBucket instanceof Entry) { + Entry thatEntry = (Entry) thatBucket; int thatHash = thatEntry.hash(); - - if ( thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { + + if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { thisBuckets[i] = thatEntry; } else { - thisBuckets[i] = new BucketGroup( - thisHash, thisEntry, - thatHash, thatEntry); + thisBuckets[i] = + new BucketGroup( + thisHash, thisEntry, + thatHash, thatEntry); } - } else if ( thatBucket instanceof BucketGroup ) { - BucketGroup thatGroup = (BucketGroup)thatBucket; - + } else if (thatBucket instanceof BucketGroup) { + BucketGroup thatGroup = (BucketGroup) thatBucket; + // Clone the other group, then place this entry into that group BucketGroup thisNewGroup = thatGroup.cloneChain(); - + Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag()); - if ( incomingEntry != null ) { + if (incomingEntry != null) { // there's already an entry w/ the same tag from the incoming TagMap - done thisBuckets[i] = thisNewGroup; - } else if ( thisNewGroup.insertInChain(thisHash, thisEntry) ) { + } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) { // able to add thisEntry into the existing groups thisBuckets[i] = thisNewGroup; } else { @@ -397,293 +389,293 @@ public final void putAll(TagMap that) { thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); } } - } else if ( thisBucket instanceof BucketGroup ) { + } else if (thisBucket instanceof BucketGroup) { // This bucket is a BucketGroup, medium to hard case // If the other side is an entry, just normal insertion procedure - no cloning required - BucketGroup thisGroup = (BucketGroup)thisBucket; - - if ( thatBucket instanceof Entry ) { - Entry thatEntry = (Entry)thatBucket; + BucketGroup thisGroup = (BucketGroup) thisBucket; + + if (thatBucket instanceof Entry) { + Entry thatEntry = (Entry) thatBucket; int thatHash = thatEntry.hash(); - - if ( !thisGroup.replaceOrInsertInChain(thatHash, thatEntry) ) { + + if (!thisGroup.replaceOrInsertInChain(thatHash, thatEntry)) { thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup); } - } else if ( thatBucket instanceof BucketGroup ) { + } else if (thatBucket instanceof BucketGroup) { // Most complicated case - need to walk that bucket group chain and update this chain - BucketGroup thatGroup = (BucketGroup)thatBucket; - + BucketGroup thatGroup = (BucketGroup) thatBucket; + thisBuckets[i] = thisGroup.replaceOrInsertAllInChain(thatGroup); } } } } - + public final void putAll(Map map) { this.checkWriteAccess(); - - if ( map instanceof TagMap ) { - this.putAll((TagMap)map); + + if (map instanceof TagMap) { + this.putAll((TagMap) map); } else { - for ( Map.Entry entry: map.entrySet() ) { + for (Map.Entry entry : map.entrySet()) { this.put(entry.getKey(), entry.getValue()); } } } - + public final void fillMap(Map map) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + map.put(thisEntry.tag, thisEntry.objectValue()); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.fillMapFromChain(map); } } } - + public final void fillStringMap(Map stringMap) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + stringMap.put(thisEntry.tag, thisEntry.stringValue()); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.fillStringMapFromChain(stringMap); } } } - + @Deprecated @Override public final Object remove(Object tag) { - if ( !(tag instanceof String) ) return null; - - Entry entry = this.removeEntry((String)tag); + if (!(tag instanceof String)) return null; + + Entry entry = this.removeEntry((String) tag); return entry == null ? null : entry.objectValue(); } - + public final Entry removeEntry(String tag) { this.checkWriteAccess(); - + Object[] thisBuckets = this.buckets; int hash = _hash(tag); int bucketIndex = hash & (thisBuckets.length - 1); - + Object bucket = thisBuckets[bucketIndex]; // null bucket case - do nothing - if ( bucket instanceof Entry ) { - Entry existingEntry = (Entry)bucket; + if (bucket instanceof Entry) { + Entry existingEntry = (Entry) bucket; if (existingEntry.matches(tag)) { thisBuckets[bucketIndex] = null; return existingEntry; } else { return null; } - } else if ( bucket instanceof BucketGroup) { - BucketGroup lastGroup = (BucketGroup)bucket; - + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag); - if ( containingGroup == null ) { + if (containingGroup == null) { return null; } - + Entry existingEntry = containingGroup._remove(hash, tag); - if ( containingGroup._isEmpty() ) { + if (containingGroup._isEmpty()) { this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup); } - + return existingEntry; } return null; } - + public final TagMap copy() { TagMap copy = new TagMap(); copy.putAll(this); return copy; } - + public final TagMap immutableCopy() { - if ( this.frozen ) { + if (this.frozen) { return this; } else { return this.copy().freeze(); } } - + public final TagMap immutable() { // specialized constructor, freezes map immediately return new TagMap(this.buckets); } - + @Override public final Iterator iterator() { return new EntryIterator(this); } - + public final Stream stream() { - return StreamSupport.stream(spliterator(), false); + return StreamSupport.stream(spliterator(), false); } - + public final void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + consumer.accept(thisEntry); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.forEachInChain(consumer); } } } - + public final void forEach(T thisObj, BiConsumer consumer) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + consumer.accept(thisObj, thisEntry); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.forEachInChain(thisObj, consumer); } } } - - public final void forEach(T thisObj, U otherObj, TriConsumer consumer) { + + public final void forEach( + T thisObj, U otherObj, TriConsumer consumer) { Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; - + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + consumer.accept(thisObj, otherObj, thisEntry); - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + thisGroup.forEachInChain(thisObj, otherObj, consumer); } } } - + public final void clear() { this.checkWriteAccess(); - + Arrays.fill(this.buckets, null); } - + public final TagMap freeze() { this.frozen = true; - + return this; } - + public boolean isFrozen() { - return this.frozen; - } - -// final void check() { -// Object[] thisBuckets = this.buckets; -// -// for ( int i = 0; i < thisBuckets.length; ++i ) { -// Object thisBucket = thisBuckets[i]; -// -// if ( thisBucket instanceof Entry ) { -// Entry thisEntry = (Entry)thisBucket; -// int thisHash = thisEntry.hash(); -// -// int expectedBucket = thisHash & (thisBuckets.length - 1); -// assert expectedBucket == i; -// } else if ( thisBucket instanceof BucketGroup ) { -// BucketGroup thisGroup = (BucketGroup)thisBucket; -// -// for ( BucketGroup curGroup = thisGroup; -// curGroup != null; -// curGroup = curGroup.prev ) -// { -// for ( int j = 0; j < BucketGroup.LEN; ++j ) { -// Entry thisEntry = curGroup._entryAt(i); -// if ( thisEntry == null ) continue; -// -// int thisHash = thisEntry.hash(); -// assert curGroup._hashAt(i) == thisHash; -// -// int expectedBucket = thisHash & (thisBuckets.length - 1); -// assert expectedBucket == i; -// } -// } -// } -// } -// } - + return this.frozen; + } + + // final void check() { + // Object[] thisBuckets = this.buckets; + // + // for ( int i = 0; i < thisBuckets.length; ++i ) { + // Object thisBucket = thisBuckets[i]; + // + // if ( thisBucket instanceof Entry ) { + // Entry thisEntry = (Entry)thisBucket; + // int thisHash = thisEntry.hash(); + // + // int expectedBucket = thisHash & (thisBuckets.length - 1); + // assert expectedBucket == i; + // } else if ( thisBucket instanceof BucketGroup ) { + // BucketGroup thisGroup = (BucketGroup)thisBucket; + // + // for ( BucketGroup curGroup = thisGroup; + // curGroup != null; + // curGroup = curGroup.prev ) + // { + // for ( int j = 0; j < BucketGroup.LEN; ++j ) { + // Entry thisEntry = curGroup._entryAt(i); + // if ( thisEntry == null ) continue; + // + // int thisHash = thisEntry.hash(); + // assert curGroup._hashAt(i) == thisHash; + // + // int expectedBucket = thisHash & (thisBuckets.length - 1); + // assert expectedBucket == i; + // } + // } + // } + // } + // } + @Override public String toString() { return toInternalString(); } - + String toPrettyString() { boolean first = true; - + StringBuilder builder = new StringBuilder(128); builder.append('{'); - for ( Entry entry: this ) { - if ( first ) { + for (Entry entry : this) { + if (first) { first = false; } else { builder.append(","); } - + builder.append(entry.tag).append('=').append(entry.stringValue()); } builder.append('}'); return builder.toString(); } - + String toInternalString() { Object[] thisBuckets = this.buckets; - + StringBuilder builder = new StringBuilder(128); - for ( int i = 0; i < thisBuckets.length; ++i ) { + for (int i = 0; i < thisBuckets.length; ++i) { builder.append('[').append(i).append("] = "); - + Object thisBucket = thisBuckets[i]; - if ( thisBucket == null ) { + if (thisBucket == null) { builder.append("null"); - } else if ( thisBucket instanceof Entry ) { + } else if (thisBucket instanceof Entry) { builder.append('{').append(thisBucket).append('}'); - } else if ( thisBucket instanceof BucketGroup ) { - for ( BucketGroup curGroup = (BucketGroup)thisBucket; - curGroup != null; - curGroup = curGroup.prev ) - { + } else if (thisBucket instanceof BucketGroup) { + for (BucketGroup curGroup = (BucketGroup) thisBucket; + curGroup != null; + curGroup = curGroup.prev) { builder.append(curGroup).append(" -> "); } } @@ -691,16 +683,16 @@ String toInternalString() { } return builder.toString(); } - + public final void checkWriteAccess() { - if ( this.frozen ) throw new IllegalStateException("TagMap frozen"); + if (this.frozen) throw new IllegalStateException("TagMap frozen"); } - + static final int _hash(String tag) { int hash = tag.hashCode(); return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); } - + public static final class Entry implements Map.Entry { /* * Special value used to record removals in the builder @@ -714,7 +706,7 @@ public static final class Entry implements Map.Entry { */ public static final byte ANY = 0; public static final byte OBJECT = 1; - + /* * Non-numeric primitive types */ @@ -731,26 +723,25 @@ public static final class Entry implements Map.Entry { public static final byte FLOAT = 8; public static final byte DOUBLE = 9; - static final Entry newAnyEntry(String tag, Object value) { - // DQH - To keep entry creation (e.g. map changes) as fast as possible, + // DQH - To keep entry creation (e.g. map changes) as fast as possible, // the entry construction is kept as simple as possible. - - // Prior versions of this code did type detection on value to - // recognize box types but that proved expensive. So now, - // the type is recorded as an ANY which is an indicator to do + + // Prior versions of this code did type detection on value to + // recognize box types but that proved expensive. So now, + // the type is recorded as an ANY which is an indicator to do // type detection later if need be. return new Entry(tag, ANY, 0L, value); } static final Entry newObjectEntry(String tag, Object value) { return new Entry(tag, OBJECT, 0, value); - } + } static final Entry newBooleanEntry(String tag, boolean value) { return new Entry(tag, BOOLEAN, boolean2Prim(value), Boolean.valueOf(value)); } - + static final Entry newBooleanEntry(String tag, Boolean box) { return new Entry(tag, BOOLEAN, boolean2Prim(box.booleanValue()), box); } @@ -758,7 +749,7 @@ static final Entry newBooleanEntry(String tag, Boolean box) { static final Entry newIntEntry(String tag, int value) { return new Entry(tag, INT, int2Prim(value), null); } - + static final Entry newIntEntry(String tag, Integer box) { return new Entry(tag, INT, int2Prim(box.intValue()), box); } @@ -766,7 +757,7 @@ static final Entry newIntEntry(String tag, Integer box) { static final Entry newLongEntry(String tag, long value) { return new Entry(tag, LONG, long2Prim(value), null); } - + static final Entry newLongEntry(String tag, Long box) { return new Entry(tag, LONG, long2Prim(box.longValue()), box); } @@ -774,7 +765,7 @@ static final Entry newLongEntry(String tag, Long box) { static final Entry newFloatEntry(String tag, float value) { return new Entry(tag, FLOAT, float2Prim(value), null); } - + static final Entry newFloatEntry(String tag, Float box) { return new Entry(tag, FLOAT, float2Prim(box.floatValue()), box); } @@ -782,7 +773,7 @@ static final Entry newFloatEntry(String tag, Float box) { static final Entry newDoubleEntry(String tag, double value) { return new Entry(tag, DOUBLE, double2Prim(value), null); } - + static final Entry newDoubleEntry(String tag, Double box) { return new Entry(tag, DOUBLE, double2Prim(box.doubleValue()), box); } @@ -790,24 +781,26 @@ static final Entry newDoubleEntry(String tag, Double box) { static final Entry newRemovalEntry(String tag) { return new Entry(tag, REMOVED, 0, null); } - + final String tag; int hash; // To optimize construction of Entry around boxed primitives and Object entries, // no type checks are done during construction. - // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into obj - // If an ANY entry is later type checked or request as a primitive, then the ANY will be resolved + // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into + // obj + // If an ANY entry is later type checked or request as a primitive, then the ANY will be + // resolved // to the correct type. - + // From the outside perspective, this object remains functionally immutable. // However, internally, it is important to remember that this type must be thread safe. // That includes multiple threads racing to resolve an ANY entry at the same time. - + volatile byte type; volatile long prim; volatile Object obj; - + volatile String strCache = null; private Entry(String tag, byte type, long prim, Object obj) { @@ -821,11 +814,11 @@ private Entry(String tag, byte type, long prim, Object obj) { public final String tag() { return this.tag; } - + int hash() { int hash = this.hash; - if ( hash != 0 ) return hash; - + if (hash != 0) return hash; + hash = _hash(this.tag); this.hash = hash; return hash; @@ -837,9 +830,9 @@ public final byte type() { public final boolean is(byte type) { byte curType = this.type; - if ( curType == type ) { + if (curType == type) { return true; - } else if ( curType != ANY ) { + } else if (curType != ANY) { return false; } else { return (this.resolveAny() == type); @@ -848,32 +841,32 @@ public final boolean is(byte type) { public final boolean isNumericPrimitive() { byte curType = this.type; - if ( _isNumericPrimitive(curType) ) { + if (_isNumericPrimitive(curType)) { return true; - } else if ( curType != ANY ) { + } else if (curType != ANY) { return false; } else { return _isNumericPrimitive(this.resolveAny()); } } - + public final boolean isNumber() { byte curType = this.type; return _isNumericPrimitive(curType) || (this.obj instanceof Number); } - + private static final boolean _isNumericPrimitive(byte type) { return (type >= BYTE); } - + private final byte resolveAny() { byte curType = this.type; - if ( curType != ANY ) return curType; - + if (curType != ANY) return curType; + Object value = this.obj; long prim; byte resolvedType; - + if (value instanceof Boolean) { Boolean boolValue = (Boolean) value; prim = boolean2Prim(boolValue); @@ -898,16 +891,16 @@ private final byte resolveAny() { prim = 0; resolvedType = OBJECT; } - + this._setPrim(resolvedType, prim); - + return resolvedType; } - + private void _setPrim(byte type, long prim) { // Order is important here, the contract is that prim must be set properly *before* // type is set to a non-object type - + this.prim = prim; this.type = type; } @@ -919,39 +912,39 @@ public final boolean isObject() { public final boolean isRemoval() { return this.is(REMOVED); } - + public final boolean matches(String tag) { return this.tag.equals(tag); } public final Object objectValue() { - if ( this.obj != null ) { + if (this.obj != null) { return this.obj; } // This code doesn't need to handle ANY-s. // An entry that starts as an ANY will always have this.obj set switch (this.type) { - case BOOLEAN: - this.obj = prim2Boolean(this.prim); - break; + case BOOLEAN: + this.obj = prim2Boolean(this.prim); + break; - case INT: - // Maybe use a wider cache that handles response code??? - this.obj = prim2Int(this.prim); - break; + case INT: + // Maybe use a wider cache that handles response code??? + this.obj = prim2Int(this.prim); + break; - case LONG: - this.obj = prim2Long(this.prim); - break; + case LONG: + this.obj = prim2Long(this.prim); + break; - case FLOAT: - this.obj = prim2Float(this.prim); - break; + case FLOAT: + this.obj = prim2Float(this.prim); + break; - case DOUBLE: - this.obj = prim2Double(this.prim); - break; + case DOUBLE: + this.obj = prim2Double(this.prim); + break; } if (this.is(REMOVED)) { @@ -963,37 +956,37 @@ public final Object objectValue() { public final boolean booleanValue() { byte type = this.type; - + if (type == BOOLEAN) { return prim2Boolean(this.prim); } else if (type == ANY && this.obj instanceof Boolean) { - boolean boolValue = (Boolean)this.obj; + boolean boolValue = (Boolean) this.obj; this._setPrim(BOOLEAN, boolean2Prim(boolValue)); return boolValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case INT: - return prim2Int(prim) != 0; + case INT: + return prim2Int(prim) != 0; - case LONG: - return prim2Long(prim) != 0L; + case LONG: + return prim2Long(prim) != 0L; - case FLOAT: - return prim2Float(prim) != 0F; + case FLOAT: + return prim2Float(prim) != 0F; - case DOUBLE: - return prim2Double(prim) != 0D; + case DOUBLE: + return prim2Double(prim) != 0D; - case OBJECT: - return (this.obj != null); + case OBJECT: + return (this.obj != null); - case REMOVED: - return false; + case REMOVED: + return false; } return false; @@ -1001,37 +994,37 @@ public final boolean booleanValue() { public final int intValue() { byte type = this.type; - + if (type == INT) { return prim2Int(this.prim); } else if (type == ANY && this.obj instanceof Integer) { - int intValue = (Integer)this.obj; + int intValue = (Integer) this.obj; this._setPrim(INT, int2Prim(intValue)); return intValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1 : 0; + case BOOLEAN: + return prim2Boolean(prim) ? 1 : 0; - case LONG: - return (int) prim2Long(prim); + case LONG: + return (int) prim2Long(prim); - case FLOAT: - return (int) prim2Float(prim); + case FLOAT: + return (int) prim2Float(prim); - case DOUBLE: - return (int) prim2Double(prim); + case DOUBLE: + return (int) prim2Double(prim); - case OBJECT: - return 0; + case OBJECT: + return 0; - case REMOVED: - return 0; + case REMOVED: + return 0; } return 0; @@ -1039,15 +1032,15 @@ public final int intValue() { public final long longValue() { byte type = this.type; - + if (type == LONG) { return prim2Long(this.prim); } else if (type == ANY && this.obj instanceof Long) { - long longValue = (Long)this.obj; + long longValue = (Long) this.obj; this._setPrim(LONG, long2Prim(longValue)); return longValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; @@ -1077,37 +1070,37 @@ public final long longValue() { public final float floatValue() { byte type = this.type; - + if (type == FLOAT) { return prim2Float(this.prim); } else if (type == ANY && this.obj instanceof Float) { - float floatValue = (Float)this.obj; + float floatValue = (Float) this.obj; this._setPrim(FLOAT, float2Prim(floatValue)); return floatValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1F : 0F; + case BOOLEAN: + return prim2Boolean(prim) ? 1F : 0F; - case INT: - return (float) prim2Int(prim); + case INT: + return (float) prim2Int(prim); - case LONG: - return (float) prim2Long(prim); + case LONG: + return (float) prim2Long(prim); - case DOUBLE: - return (float) prim2Double(prim); + case DOUBLE: + return (float) prim2Double(prim); - case OBJECT: - return 0F; + case OBJECT: + return 0F; - case REMOVED: - return 0F; + case REMOVED: + return 0F; } return 0F; @@ -1115,37 +1108,37 @@ public final float floatValue() { public final double doubleValue() { byte type = this.type; - + if (type == DOUBLE) { return prim2Double(this.prim); } else if (type == ANY && this.obj instanceof Double) { - double doubleValue = (Double)this.obj; + double doubleValue = (Double) this.obj; this._setPrim(DOUBLE, double2Prim(doubleValue)); return doubleValue; } - + // resolution will set prim if necessary byte resolvedType = this.resolveAny(); long prim = this.prim; switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1D : 0D; + case BOOLEAN: + return prim2Boolean(prim) ? 1D : 0D; - case INT: - return (double) prim2Int(prim); + case INT: + return (double) prim2Int(prim); - case LONG: - return (double) prim2Long(prim); + case LONG: + return (double) prim2Long(prim); - case FLOAT: - return (double) prim2Float(prim); + case FLOAT: + return (double) prim2Float(prim); - case OBJECT: - return 0D; + case OBJECT: + return 0D; - case REMOVED: - return 0D; + case REMOVED: + return 0D; } return 0D; @@ -1153,62 +1146,62 @@ public final double doubleValue() { public final String stringValue() { String strCache = this.strCache; - if ( strCache != null ) { - return strCache; + if (strCache != null) { + return strCache; } - + String computeStr = this.computeStringValue(); this.strCache = computeStr; return computeStr; } - + private final String computeStringValue() { - // Could do type resolution here, + // Could do type resolution here, // but decided to just fallback to this.obj.toString() for ANY case switch (this.type) { - case BOOLEAN: - return Boolean.toString(prim2Boolean(this.prim)); + case BOOLEAN: + return Boolean.toString(prim2Boolean(this.prim)); - case INT: - return Integer.toString(prim2Int(this.prim)); + case INT: + return Integer.toString(prim2Int(this.prim)); - case LONG: - return Long.toString(prim2Long(this.prim)); + case LONG: + return Long.toString(prim2Long(this.prim)); - case FLOAT: - return Float.toString(prim2Float(this.prim)); + case FLOAT: + return Float.toString(prim2Float(this.prim)); - case DOUBLE: - return Double.toString(prim2Double(this.prim)); + case DOUBLE: + return Double.toString(prim2Double(this.prim)); - case REMOVED: - return null; + case REMOVED: + return null; - case OBJECT: - case ANY: - return this.obj.toString(); + case OBJECT: + case ANY: + return this.obj.toString(); } return null; } - + @Override public final String toString() { return this.tag() + '=' + this.stringValue(); } - + @Deprecated @Override public String getKey() { return this.tag(); } - + @Deprecated @Override public Object getValue() { return this.objectValue(); } - + @Deprecated @Override public Object setValue(Object value) { @@ -1255,531 +1248,537 @@ private static final double prim2Double(long prim) { return Double.longBitsToDouble(prim); } } - + public static final class Builder implements Iterable { private Entry[] entries; private int nextPos = 0; - + private Builder() { this(8); } - + private Builder(int size) { this.entries = new Entry[size]; } - + public final boolean isDefinitelyEmpty() { return (this.nextPos == 0); } - + /** - * Provides the estimated size of the map created by the builder - * Doesn't account for overwritten entries or entry removal + * Provides the estimated size of the map created by the builder Doesn't account for overwritten + * entries or entry removal + * * @return */ public final int estimateSize() { return this.nextPos; } - + public final Builder put(String tag, Object value) { return this.put(Entry.newAnyEntry(tag, value)); } - + public final Builder put(String tag, CharSequence value) { return this.put(Entry.newObjectEntry(tag, value)); } - + public final Builder put(String tag, boolean value) { return this.put(Entry.newBooleanEntry(tag, value)); } - + public final Builder put(String tag, int value) { return this.put(Entry.newIntEntry(tag, value)); } - + public final Builder put(String tag, long value) { return this.put(Entry.newLongEntry(tag, value)); } - + public final Builder put(String tag, float value) { return this.put(Entry.newFloatEntry(tag, value)); } - + public final Builder put(String tag, double value) { return this.put(Entry.newDoubleEntry(tag, value)); } - + public final Builder remove(String tag) { return this.put(Entry.newRemovalEntry(tag)); } - + public final Builder put(Entry entry) { - if ( this.nextPos >= this.entries.length ) { + if (this.nextPos >= this.entries.length) { this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); } - + this.entries[this.nextPos++] = entry; return this; } - + public final void reset() { Arrays.fill(this.entries, null); this.nextPos = 0; } - + @Override public final Iterator iterator() { return new BuilderIterator(this.entries, this.nextPos); } - + public TagMap build() { TagMap map = new TagMap(); - if ( this.nextPos != 0 ) map.putAll(this.entries, this.nextPos); + if (this.nextPos != 0) map.putAll(this.entries, this.nextPos); return map; } - + public TagMap buildImmutable() { - if ( this.nextPos == 0 ) { + if (this.nextPos == 0) { return TagMap.EMPTY; } else { return this.build().freeze(); } } } - + private static final class BuilderIterator implements Iterator { private final Entry[] entries; private final int size; - + private int pos; - + BuilderIterator(Entry[] entries, int size) { this.entries = entries; this.size = size; - + this.pos = -1; } - + @Override public final boolean hasNext() { - return ( this.pos + 1 < this.size ); + return (this.pos + 1 < this.size); } - + @Override public Entry next() { - if ( !this.hasNext() ) throw new NoSuchElementException("no next"); - + if (!this.hasNext()) throw new NoSuchElementException("no next"); + return this.entries[++this.pos]; } } - - private static abstract class MapIterator implements Iterator { + + private abstract static class MapIterator implements Iterator { private final Object[] buckets; - + private Entry nextEntry; private int bucketIndex = -1; - + private BucketGroup group = null; private int groupIndex = 0; - + MapIterator(TagMap map) { this.buckets = map.buckets; } - + @Override public boolean hasNext() { - if ( this.nextEntry != null ) return true; - - while ( this.bucketIndex < this.buckets.length ) { + if (this.nextEntry != null) return true; + + while (this.bucketIndex < this.buckets.length) { this.nextEntry = this.advance(); - if ( this.nextEntry != null ) return true; + if (this.nextEntry != null) return true; } - + return false; } - + Entry nextEntry() { - if ( this.nextEntry != null ) { + if (this.nextEntry != null) { Entry nextEntry = this.nextEntry; this.nextEntry = null; return nextEntry; } - - if ( this.hasNext() ) { + + if (this.hasNext()) { return this.nextEntry; } else { throw new NoSuchElementException(); } } - + private final Entry advance() { - while ( this.bucketIndex < this.buckets.length ) { - if ( this.group != null ) { - for ( ++this.groupIndex; this.groupIndex < BucketGroup.LEN; ++this.groupIndex ) { + while (this.bucketIndex < this.buckets.length) { + if (this.group != null) { + for (++this.groupIndex; this.groupIndex < BucketGroup.LEN; ++this.groupIndex) { Entry tagEntry = this.group._entryAt(this.groupIndex); - if ( tagEntry != null ) return tagEntry; + if (tagEntry != null) return tagEntry; } - + // done processing - that group, go to next group this.group = this.group.prev; this.groupIndex = -1; } // if the group is null, then we've finished the current bucket - so advance the bucket - if ( this.group == null ) { - for ( ++this.bucketIndex; this.bucketIndex < this.buckets.length; ++this.bucketIndex ) { + if (this.group == null) { + for (++this.bucketIndex; this.bucketIndex < this.buckets.length; ++this.bucketIndex) { Object bucket = this.buckets[this.bucketIndex]; - - if ( bucket instanceof Entry ) { - return (Entry)bucket; - } else if ( bucket instanceof BucketGroup ) { - this.group = (BucketGroup)bucket; + + if (bucket instanceof Entry) { + return (Entry) bucket; + } else if (bucket instanceof BucketGroup) { + this.group = (BucketGroup) bucket; this.groupIndex = -1; - + break; } } } - }; - + } + ; + return null; } } - + static final class EntryIterator extends MapIterator { EntryIterator(TagMap map) { super(map); } - + @Override public Entry next() { return this.nextEntry(); } } - + /** - * BucketGroup is compromise for performance over a linked list or array - * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities - * - array - wouldn't be able to store hashes close together - * - parallel arrays (one for hashes & another for entries) would require more allocation + * BucketGroup is compromise for performance over a linked list or array - linked list - would + * prevent TagEntry-s from being immutable and would limit sharing opportunities - array - + * wouldn't be able to store hashes close together - parallel arrays (one for hashes & another for + * entries) would require more allocation */ static final class BucketGroup { static final int LEN = 4; - + // int hashFilter = 0; - + // want the hashes together in the same cache line int hash0 = 0; int hash1 = 0; int hash2 = 0; int hash3 = 0; - + Entry entry0 = null; Entry entry1 = null; Entry entry2 = null; Entry entry3 = null; - + BucketGroup prev = null; - + BucketGroup() {} - - /** - * New group with an entry pointing to existing BucketGroup - */ + + /** New group with an entry pointing to existing BucketGroup */ BucketGroup(int hash0, Entry entry0, BucketGroup prev) { this.hash0 = hash0; this.entry0 = entry0; - + this.prev = prev; - + // this.hashFilter = hash0; } - - /** - * New group composed of two entries - */ + + /** New group composed of two entries */ BucketGroup(int hash0, Entry entry0, int hash1, Entry entry1) { this.hash0 = hash0; this.entry0 = entry0; - + this.hash1 = hash1; this.entry1 = entry1; - + // this.hashFilter = hash0 | hash1; } - - /** - * New group composed of 4 entries - used for cloning - */ + + /** New group composed of 4 entries - used for cloning */ BucketGroup( - int hash0, Entry entry0, - int hash1, Entry entry1, - int hash2, Entry entry2, - int hash3, Entry entry3) - { + int hash0, + Entry entry0, + int hash1, + Entry entry1, + int hash2, + Entry entry2, + int hash3, + Entry entry3) { this.hash0 = hash0; this.entry0 = entry0; - + this.hash1 = hash1; this.entry1 = entry1; - + this.hash2 = hash2; this.entry2 = entry2; - + this.hash3 = hash3; this.entry3 = entry3; - + // this.hashFilter = hash0 | hash1 | hash2 | hash3; } - + Entry _entryAt(int index) { - switch ( index ) { + switch (index) { case 0: - return this.entry0; - + return this.entry0; + case 1: - return this.entry1; - + return this.entry1; + case 2: - return this.entry2; - + return this.entry2; + case 3: - return this.entry3; + return this.entry3; } - + return null; } - + int _hashAt(int index) { - switch ( index ) { + switch (index) { case 0: - return this.hash0; - + return this.hash0; + case 1: - return this.hash1; - + return this.hash1; + case 2: - return this.hash2; - + return this.hash2; + case 3: - return this.hash3; + return this.hash3; } - + return 0; } - + int sizeInChain() { int size = 0; - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { size += curGroup._size(); } return size; } - + int _size() { - return ( this.hash0 == 0 ? 0 : 1 ) + - ( this.hash1 == 0 ? 0 : 1 ) + - ( this.hash2 == 0 ? 0 : 1 ) + - ( this.hash3 == 0 ? 0 : 1 ); + return (this.hash0 == 0 ? 0 : 1) + + (this.hash1 == 0 ? 0 : 1) + + (this.hash2 == 0 ? 0 : 1) + + (this.hash3 == 0 ? 0 : 1); } - + boolean isEmptyChain() { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { - if ( !curGroup._isEmpty() ) return false; + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { + if (!curGroup._isEmpty()) return false; } return true; } - + boolean _isEmpty() { return (this.hash0 | this.hash1 | this.hash2 | this.hash3) == 0; // return (this.hashFilter == 0); } - + BucketGroup findContainingGroupInChain(int hash, String tag) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { - if ( curGroup._find(hash, tag) != null ) return curGroup; + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { + if (curGroup._find(hash, tag) != null) return curGroup; } return null; } - -// boolean _mayContain(int hash) { -// return ((hash & this.hashFilter) == hash); -// } - + + // boolean _mayContain(int hash) { + // return ((hash & this.hashFilter) == hash); + // } + Entry findInChain(int hash, String tag) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { Entry curEntry = curGroup._find(hash, tag); - if ( curEntry != null ) return curEntry; + if (curEntry != null) return curEntry; } return null; } Entry _find(int hash, String tag) { // if ( this._mayContain(hash) ) return null; - - if ( this.hash0 == hash && this.entry0.matches(tag)) { + + if (this.hash0 == hash && this.entry0.matches(tag)) { return this.entry0; - } else if ( this.hash1 == hash && this.entry1.matches(tag)) { + } else if (this.hash1 == hash && this.entry1.matches(tag)) { return this.entry1; - } else if ( this.hash2 == hash && this.entry2.matches(tag)) { + } else if (this.hash2 == hash && this.entry2.matches(tag)) { return this.entry2; - } else if ( this.hash3 == hash && this.entry3.matches(tag)) { + } else if (this.hash3 == hash && this.entry3.matches(tag)) { return this.entry3; } return null; } - + BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) { BucketGroup thisOrigHeadGroup = this; BucketGroup thisNewestHeadGroup = thisOrigHeadGroup; - - for ( BucketGroup thatCurGroup = thatHeadGroup; - thatCurGroup != null; - thatCurGroup = thatCurGroup.prev ) - { + + for (BucketGroup thatCurGroup = thatHeadGroup; + thatCurGroup != null; + thatCurGroup = thatCurGroup.prev) { // First phase - tries to replace or insert each entry in the existing bucket chain // Only need to search the original groups for replacements // The whole chain is eligible for insertions - boolean handled0 = (thatCurGroup.hash0 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash0, thatCurGroup.entry0) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash0, thatCurGroup.entry0); - - boolean handled1 = (thatCurGroup.hash1 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash1, thatCurGroup.entry1) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash1, thatCurGroup.entry1); - - boolean handled2 = (thatCurGroup.hash2 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash2, thatCurGroup.entry2) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash2, thatCurGroup.entry2); - - boolean handled3 = (thatCurGroup.hash3 == 0) || - (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash3, thatCurGroup.entry3) != null) || - thisNewestHeadGroup.insertInChain(thatCurGroup.hash3, thatCurGroup.entry3); - - // Second phase - takes any entries that weren't handled by phase 1 and puts them - // into a new BucketGroup. Since BucketGroups are fixed size, we know that the + boolean handled0 = + (thatCurGroup.hash0 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash0, thatCurGroup.entry0) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash0, thatCurGroup.entry0); + + boolean handled1 = + (thatCurGroup.hash1 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash1, thatCurGroup.entry1) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash1, thatCurGroup.entry1); + + boolean handled2 = + (thatCurGroup.hash2 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash2, thatCurGroup.entry2) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash2, thatCurGroup.entry2); + + boolean handled3 = + (thatCurGroup.hash3 == 0) + || (thisOrigHeadGroup.replaceInChain(thatCurGroup.hash3, thatCurGroup.entry3) + != null) + || thisNewestHeadGroup.insertInChain(thatCurGroup.hash3, thatCurGroup.entry3); + + // Second phase - takes any entries that weren't handled by phase 1 and puts them + // into a new BucketGroup. Since BucketGroups are fixed size, we know that the // left over entries from one BucketGroup will fit in the new BucketGroup. - if ( !handled0 || !handled1 || !handled2 || !handled3 ) { + if (!handled0 || !handled1 || !handled2 || !handled3) { // Rather than calling insert one time per entry // Exploiting the fact that the new group is known to be empty // And that BucketGroups are allowed to have holes in them (to allow for removal), - // so each unhandled entry from the source group is simply placed in + // so each unhandled entry from the source group is simply placed in // the same slot in the new group BucketGroup thisNewHashGroup = new BucketGroup(); int hashFilter = 0; - if ( !handled0 ) { + if (!handled0) { thisNewHashGroup.hash0 = thatCurGroup.hash0; thisNewHashGroup.entry0 = thatCurGroup.entry0; hashFilter |= thatCurGroup.hash0; } - if ( !handled1 ) { + if (!handled1) { thisNewHashGroup.hash1 = thatCurGroup.hash1; thisNewHashGroup.entry1 = thatCurGroup.entry1; hashFilter |= thatCurGroup.hash1; } - if ( !handled2 ) { + if (!handled2) { thisNewHashGroup.hash2 = thatCurGroup.hash2; thisNewHashGroup.entry2 = thatCurGroup.entry2; hashFilter |= thatCurGroup.hash2; } - if ( !handled3 ) { + if (!handled3) { thisNewHashGroup.hash3 = thatCurGroup.hash3; thisNewHashGroup.entry3 = thatCurGroup.entry3; hashFilter |= thatCurGroup.hash3; } // thisNewHashGroup.hashFilter = hashFilter; thisNewHashGroup.prev = thisNewestHeadGroup; - + thisNewestHeadGroup = thisNewHashGroup; } } - + return thisNewestHeadGroup; } - + boolean replaceOrInsertInChain(int hash, Entry entry) { - return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry); + return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry); } - + Entry replaceInChain(int hash, Entry entry) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { Entry prevEntry = curGroup._replace(hash, entry); - if ( prevEntry != null ) return prevEntry; + if (prevEntry != null) return prevEntry; } return null; } - + Entry _replace(int hash, Entry entry) { // if ( this._mayContain(hash) ) return null; - + // first check to see if the item is already present Entry prevEntry = null; - if ( this.hash0 == hash && this.entry0.matches(entry.tag) ) { + if (this.hash0 == hash && this.entry0.matches(entry.tag)) { prevEntry = this.entry0; this.entry0 = entry; - } else if ( this.hash1 == hash && this.entry1.matches(entry.tag) ) { + } else if (this.hash1 == hash && this.entry1.matches(entry.tag)) { prevEntry = this.entry1; this.entry1 = entry; - } else if ( this.hash2 == hash && this.entry2.matches(entry.tag) ) { + } else if (this.hash2 == hash && this.entry2.matches(entry.tag)) { prevEntry = this.entry2; this.entry2 = entry; - } else if ( this.hash3 == hash && this.entry3.matches(entry.tag) ) { + } else if (this.hash3 == hash && this.entry3.matches(entry.tag)) { prevEntry = this.entry3; this.entry3 = entry; } - + // no need to update this.hashFilter, since the hash is already included return prevEntry; } - + boolean insertInChain(int hash, Entry entry) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { - if ( curGroup._insert(hash, entry) ) return true; + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { + if (curGroup._insert(hash, entry)) return true; } return false; } - + boolean _insert(int hash, Entry entry) { boolean inserted = false; - if ( this.hash0 == 0 ) { + if (this.hash0 == 0) { this.hash0 = hash; this.entry0 = entry; - + // this.hashFilter |= hash; inserted = true; - } else if ( this.hash1 == 0 ) { + } else if (this.hash1 == 0) { this.hash1 = hash; this.entry1 = entry; - + // this.hashFilter |= hash; inserted = true; - } else if ( this.hash2 == 0 ) { + } else if (this.hash2 == 0) { this.hash2 = hash; this.entry2 = entry; - + // this.hashFilter |= hash; inserted = true; - } else if ( this.hash3 == 0 ) { + } else if (this.hash3 == 0) { this.hash3 = hash; this.entry3 = entry; - + // this.hashFilter |= hash; inserted = true; } return inserted; } - + BucketGroup removeGroupInChain(BucketGroup removeGroup) { BucketGroup firstGroup = this; - if ( firstGroup == removeGroup ) { + if (firstGroup == removeGroup) { return firstGroup.prev; } - - for ( BucketGroup priorGroup = firstGroup, curGroup = priorGroup.prev; - curGroup != null; - priorGroup = curGroup, curGroup = priorGroup.prev ) { - if ( curGroup == removeGroup ) { + + for (BucketGroup priorGroup = firstGroup, curGroup = priorGroup.prev; + curGroup != null; + priorGroup = curGroup, curGroup = priorGroup.prev) { + if (curGroup == removeGroup) { priorGroup.prev = curGroup.prev; } } @@ -1788,240 +1787,237 @@ BucketGroup removeGroupInChain(BucketGroup removeGroup) { Entry _remove(int hash, String tag) { Entry existingEntry = null; - if ( this.hash0 == hash && this.entry0.matches(tag)) { + if (this.hash0 == hash && this.entry0.matches(tag)) { existingEntry = this.entry0; - + this.hash0 = 0; this.entry0 = null; - } else if ( this.hash1 == hash && this.entry1.matches(tag) ) { + } else if (this.hash1 == hash && this.entry1.matches(tag)) { existingEntry = this.entry1; - + this.hash1 = 0; this.entry1 = null; - } else if ( this.hash2 == hash && this.entry2.matches(tag) ) { + } else if (this.hash2 == hash && this.entry2.matches(tag)) { existingEntry = this.entry2; - + this.hash2 = 0; this.entry2 = null; - } else if ( this.hash3 == hash && this.entry3.matches(tag) ) { + } else if (this.hash3 == hash && this.entry3.matches(tag)) { existingEntry = this.entry3; - + this.hash3 = 0; this.entry3 = null; } return existingEntry; } - + void forEachInChain(Consumer consumer) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(consumer); } } - + void _forEach(Consumer consumer) { - if ( this.entry0 != null ) consumer.accept(this.entry0); - if ( this.entry1 != null ) consumer.accept(this.entry1); - if ( this.entry2 != null ) consumer.accept(this.entry2); - if ( this.entry3 != null ) consumer.accept(this.entry3); + if (this.entry0 != null) consumer.accept(this.entry0); + if (this.entry1 != null) consumer.accept(this.entry1); + if (this.entry2 != null) consumer.accept(this.entry2); + if (this.entry3 != null) consumer.accept(this.entry3); } - + void forEachInChain(T thisObj, BiConsumer consumer) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(thisObj, consumer); } } - + void _forEach(T thisObj, BiConsumer consumer) { - if ( this.entry0 != null ) consumer.accept(thisObj, this.entry0); - if ( this.entry1 != null ) consumer.accept(thisObj, this.entry1); - if ( this.entry2 != null ) consumer.accept(thisObj, this.entry2); - if ( this.entry3 != null ) consumer.accept(thisObj, this.entry3); + if (this.entry0 != null) consumer.accept(thisObj, this.entry0); + if (this.entry1 != null) consumer.accept(thisObj, this.entry1); + if (this.entry2 != null) consumer.accept(thisObj, this.entry2); + if (this.entry3 != null) consumer.accept(thisObj, this.entry3); } - + void forEachInChain(T thisObj, U otherObj, TriConsumer consumer) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._forEach(thisObj, otherObj, consumer); } } void _forEach(T thisObj, U otherObj, TriConsumer consumer) { - if ( this.entry0 != null ) consumer.accept(thisObj, otherObj, this.entry0); - if ( this.entry1 != null ) consumer.accept(thisObj, otherObj, this.entry1); - if ( this.entry2 != null ) consumer.accept(thisObj, otherObj, this.entry2); - if ( this.entry3 != null ) consumer.accept(thisObj, otherObj, this.entry3); + if (this.entry0 != null) consumer.accept(thisObj, otherObj, this.entry0); + if (this.entry1 != null) consumer.accept(thisObj, otherObj, this.entry1); + if (this.entry2 != null) consumer.accept(thisObj, otherObj, this.entry2); + if (this.entry3 != null) consumer.accept(thisObj, otherObj, this.entry3); } - + void fillMapFromChain(Map map) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._fillMap(map); } } - + void _fillMap(Map map) { Entry entry0 = this.entry0; - if ( entry0 != null ) map.put(entry0.tag, entry0.objectValue()); + if (entry0 != null) map.put(entry0.tag, entry0.objectValue()); Entry entry1 = this.entry1; - if ( entry1 != null ) map.put(entry1.tag, entry1.objectValue()); - + if (entry1 != null) map.put(entry1.tag, entry1.objectValue()); + Entry entry2 = this.entry2; - if ( entry2 != null ) map.put(entry2.tag, entry2.objectValue()); - + if (entry2 != null) map.put(entry2.tag, entry2.objectValue()); + Entry entry3 = this.entry3; - if ( entry3 != null ) map.put(entry3.tag, entry3.objectValue()); + if (entry3 != null) map.put(entry3.tag, entry3.objectValue()); } - + void fillStringMapFromChain(Map map) { - for ( BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev ) { + for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { curGroup._fillStringMap(map); } } - + void _fillStringMap(Map map) { Entry entry0 = this.entry0; - if ( entry0 != null ) map.put(entry0.tag, entry0.stringValue()); + if (entry0 != null) map.put(entry0.tag, entry0.stringValue()); Entry entry1 = this.entry1; - if ( entry1 != null ) map.put(entry1.tag, entry1.stringValue()); - + if (entry1 != null) map.put(entry1.tag, entry1.stringValue()); + Entry entry2 = this.entry2; - if ( entry2 != null ) map.put(entry2.tag, entry2.stringValue()); - + if (entry2 != null) map.put(entry2.tag, entry2.stringValue()); + Entry entry3 = this.entry3; - if ( entry3 != null ) map.put(entry3.tag, entry3.stringValue()); + if (entry3 != null) map.put(entry3.tag, entry3.stringValue()); } - + BucketGroup cloneChain() { BucketGroup thisClone = this._cloneEntries(); - + BucketGroup thisPriorClone = thisClone; - for ( BucketGroup curGroup = this.prev; - curGroup != null; - curGroup = curGroup.prev ) - { + for (BucketGroup curGroup = this.prev; curGroup != null; curGroup = curGroup.prev) { BucketGroup newClone = curGroup._cloneEntries(); thisPriorClone.prev = newClone; - + thisPriorClone = newClone; } - + return thisClone; } - + BucketGroup _cloneEntries() { return new BucketGroup( - this.hash0, this.entry0, - this.hash1, this.entry1, - this.hash2, this.entry2, - this.hash3, this.entry3); + this.hash0, this.entry0, + this.hash1, this.entry1, + this.hash2, this.entry2, + this.hash3, this.entry3); } - + @Override public String toString() { StringBuilder builder = new StringBuilder(32); builder.append('['); - for ( int i = 0; i < BucketGroup.LEN; ++i ) { - if ( builder.length() != 0 ) builder.append(", "); - + for (int i = 0; i < BucketGroup.LEN; ++i) { + if (builder.length() != 0) builder.append(", "); + builder.append(this._entryAt(i)); } builder.append(']'); return builder.toString(); } } - + private static final class Entries extends AbstractSet> { private final TagMap map; - + Entries(TagMap map) { this.map = map; } - + @Override public int size() { return this.map.computeSize(); } - + @Override public boolean isEmpty() { return this.map.checkIfEmpty(); } - + @Override public Iterator> iterator() { @SuppressWarnings({"rawtypes", "unchecked"}) - Iterator> iter = (Iterator)this.map.iterator(); + Iterator> iter = (Iterator) this.map.iterator(); return iter; } } - + private static final class Keys extends AbstractSet { private final TagMap map; - + Keys(TagMap map) { this.map = map; } - + @Override public int size() { return this.map.computeSize(); } - + @Override public boolean isEmpty() { return this.map.checkIfEmpty(); } - + @Override public boolean contains(Object o) { return this.map.containsKey(o); } - + @Override public Iterator iterator() { return new KeysIterator(this.map); } } - + static final class KeysIterator extends MapIterator { KeysIterator(TagMap map) { super(map); } - + @Override public String next() { return this.nextEntry().tag(); } } - + private static final class Values extends AbstractCollection { private final TagMap map; - + Values(TagMap map) { this.map = map; } - + @Override public int size() { return this.map.computeSize(); } - + @Override public boolean isEmpty() { return this.map.checkIfEmpty(); } - + @Override public boolean contains(Object o) { return this.map.containsValue(o); } - + @Override public Iterator iterator() { return new ValuesIterator(this.map); } } - + static final class ValuesIterator extends MapIterator { ValuesIterator(TagMap map) { super(map); diff --git a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java index 296b93d0914..a08be92d248 100644 --- a/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java +++ b/internal-api/src/main/java/datadog/trace/api/gateway/IGSpanInfo.java @@ -1,9 +1,8 @@ package datadog.trace.api.gateway; -import datadog.trace.api.TagMap; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; -import java.util.Map; public interface IGSpanInfo { DDTraceId getTraceId(); 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 ef102de8e5f..31b610887ee 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,7 +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; 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 282261772e6..3e76d657069 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 @@ -2,8 +2,6 @@ import datadog.trace.api.TagMap; import datadog.trace.api.naming.NamingSchema; -import java.util.Collections; -import java.util.Map; import javax.annotation.Nonnull; public class PeerServiceNamingV0 implements NamingSchema.ForPeerService { @@ -14,6 +12,5 @@ public boolean supports() { @Nonnull @Override - public void tags(@Nonnull final TagMap unsafeTags) { - } + public void tags(@Nonnull final TagMap 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 4c64dda3a73..827a7489e09 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 @@ -71,8 +71,7 @@ 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 TagMap unsafeTags, @Nullable final String[] precursors) { if (precursors == null) { return false; } diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java index ea62a78008d..26e97b77e45 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/AgentSpan.java @@ -2,18 +2,16 @@ import static datadog.trace.bootstrap.instrumentation.api.InternalContextKeys.SPAN_KEY; -import datadog.trace.api.TagMap; import datadog.context.Context; import datadog.context.ContextKey; import datadog.context.ImplicitContextKeyed; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.IGSpanInfo; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.interceptor.MutableSpan; - -import java.util.Collections; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -96,7 +94,7 @@ default boolean isValid() { @Override TagMap getTags(); - + Object getTag(String key); @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java index 7754a1f92de..ebbc2e028f0 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/ExtractedSpan.java @@ -5,7 +5,6 @@ import datadog.trace.api.TraceConfig; import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; -import java.util.Collections; import java.util.Map; /** diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java index 7cb834ca520..9e5f6c33a1f 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/NoopSpan.java @@ -7,7 +7,6 @@ import datadog.trace.api.gateway.Flow.Action.RequestBlockingAction; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.sampling.PrioritySampling; -import java.util.Map; class NoopSpan extends ImmutableSpan implements AgentSpan { static final NoopSpan INSTANCE = new NoopSpan(); @@ -78,10 +77,10 @@ public Integer getSamplingPriority() { public String getSpanType() { return null; } - + @Override public TagMap getTags() { - return TagMap.EMPTY; + return TagMap.EMPTY; } @Override diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index 4c89d6a45aa..d91b41ce72e 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -3,9 +3,9 @@ import static datadog.trace.api.TracePropagationStyle.NONE; import static java.util.Collections.emptyList; -import datadog.trace.api.TagMap; import datadog.trace.api.DDSpanId; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.TracePropagationStyle; import datadog.trace.api.datastreams.PathwayContext; @@ -14,7 +14,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.TreeMap; /** * When calling extract, we allow for grabbing other configured headers as tags. Those tags are @@ -55,9 +54,9 @@ public TagContext( final TraceConfig traceConfig, final TracePropagationStyle propagationStyle, final DDTraceId traceId) { - - //if ( tags != null ) tags.checkWriteAccess(); - + + // if ( tags != null ) tags.checkWriteAccess(); + this.origin = origin; this.tags = tags; this.terminatedContextLinks = null; @@ -169,15 +168,15 @@ public String getCustomIpHeader() { } public final TagMap getTags() { - // DQH - Because of the lazy in putTag, this method effectively returns an immutable map - return ( this.tags == null ) ? TagMap.EMPTY: this.tags; + // DQH - Because of the lazy in putTag, this method effectively returns an immutable map + return (this.tags == null) ? TagMap.EMPTY : this.tags; } public void putTag(final String key, final String value) { - if ( this.tags == null ) { - this.tags = new TagMap(); - } - this.tags.set(key, value); + if (this.tags == null) { + this.tags = new TagMap(); + } + this.tags.set(key, value); } @Override diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index 987a2507551..16ad7b68226 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -8,84 +8,84 @@ public class TagMapBuilderTest { static final int SIZE = 32; - + @Test public void buildMutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + // just proving that the map is mutable map.set(key(1000), value(1000)); } - + @Test public void buildImmutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.buildImmutable(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + assertFrozen(map); } - + @Test public void buildWithRemoves() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - - for ( int i = 0; i < SIZE; i += 2 ) { + + for (int i = 0; i < SIZE; i += 2) { builder.remove(key(i)); } TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { - if ( (i % 2) == 0 ) { + for (int i = 0; i < SIZE; ++i) { + if ((i % 2) == 0) { assertNull(map.getString(key(i))); } else { assertEquals(value(i), map.getString(key(i))); } } } - + @Test public void reset() { TagMap.Builder builder = TagMap.builder(2); - + builder.put(key(0), value(0)); TagMap map0 = builder.build(); - + builder.reset(); - + builder.put(key(1), value(1)); TagMap map1 = builder.build(); - + assertEquals(value(0), map0.getString(key(0))); assertNull(map1.getString(key(0))); - + assertNull(map0.getString(key(1))); assertEquals(value(1), map1.getString(key(1))); } - + static final String key(int i) { return "key-" + i; } @@ -93,12 +93,12 @@ static final String key(int i) { static final String value(int i) { return "value-" + i; } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index cfb1b9c4929..0d66911d168 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -12,247 +12,247 @@ public void objectEntry() { TagMap.Entry entry = TagMap.Entry.newObjectEntry("foo", "bar"); assertKey("foo", entry); assertValue("bar", entry); - + assertEquals("bar", entry.stringValue()); - + assertTrue(entry.isObject()); } - + @Test public void anyEntry_object() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", "bar"); - + assertKey("foo", entry); assertValue("bar", entry); - + assertTrue(entry.isObject()); - + assertKey("foo", entry); assertValue("bar", entry); } - + @Test public void booleanEntry() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", true); - + assertKey("foo", entry); assertValue(true, entry); - + assertFalse(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.BOOLEAN)); } - + @Test public void booleanEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); - + assertKey("foo", entry); assertValue(true, entry); - + assertFalse(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.BOOLEAN)); } - + @Test public void anyEntry_boolean() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); assertKey("foo", entry); assertValue(true, entry); - + assertFalse(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.BOOLEAN)); - + assertValue(true, entry); } - + @Test public void intEntry() { TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", 20); - + assertKey("foo", entry); assertValue(20, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.INT)); } - + @Test public void intEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)); - + assertKey("foo", entry); assertValue(20, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.INT)); } - + @Test public void anyEntry_int() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)); assertKey("foo", entry); assertValue(20, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.INT)); assertValue(20, entry); } - + @Test public void longEntry() { TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", 1_048_576L); - + assertKey("foo", entry); assertValue(1_048_576L, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.LONG)); } - + @Test public void longEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)); - + assertKey("foo", entry); assertValue(1_048_576L, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.LONG)); } - + @Test public void anyEntry_long() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)); - + assertKey("foo", entry); assertValue(1_048_576L, entry); - + // type checks force any resolution assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.LONG)); - + // check value again after resolution assertValue(1_048_576L, entry); } - + @Test public void doubleEntry() { - TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); - + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); + assertKey("foo", entry); assertValue(Math.PI, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.DOUBLE)); } - + @Test public void doubleEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); - + TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); + assertKey("foo", entry); assertValue(Math.PI, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.DOUBLE)); } - + @Test public void anyEntry_double() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)); - + assertKey("foo", entry); assertValue(Math.PI, entry); - + // type checks force any resolution assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.DOUBLE)); - + // check value again after resolution assertValue(Math.PI, entry); } - + @Test public void floatEntry() { - TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); - + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); + assertKey("foo", entry); assertValue(2.718281828f, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.FLOAT)); } - + @Test public void floatEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); - + TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); + assertKey("foo", entry); assertValue(2.718281828f, entry); - + assertTrue(entry.isNumericPrimitive()); assertTrue(entry.is(TagMap.Entry.FLOAT)); } - + static final void assertKey(String expected, TagMap.Entry entry) { assertEquals(expected, entry.tag()); assertEquals(expected, entry.getKey()); } - + static final void assertValue(Object expected, TagMap.Entry entry) { assertEquals(expected, entry.objectValue()); assertEquals(expected, entry.getValue()); - + assertEquals(expected.toString(), entry.stringValue()); } - + static final void assertValue(boolean expected, TagMap.Entry entry) { assertEquals(expected, entry.booleanValue()); assertEquals(Boolean.valueOf(expected), entry.objectValue()); - + assertEquals(Boolean.toString(expected), entry.stringValue()); } - + static final void assertValue(int expected, TagMap.Entry entry) { assertEquals(expected, entry.intValue()); - assertEquals((long)expected, entry.longValue()); - assertEquals((float)expected, entry.floatValue()); - assertEquals((double)expected, entry.doubleValue()); + assertEquals((long) expected, entry.longValue()); + assertEquals((float) expected, entry.floatValue()); + assertEquals((double) expected, entry.doubleValue()); assertEquals(Integer.valueOf(expected), entry.objectValue()); - + assertEquals(Integer.toString(expected), entry.stringValue()); } - + static final void assertValue(long expected, TagMap.Entry entry) { assertEquals(expected, entry.longValue()); - assertEquals((int)expected, entry.intValue()); - assertEquals((float)expected, entry.floatValue()); - assertEquals((double)expected, entry.doubleValue()); + assertEquals((int) expected, entry.intValue()); + assertEquals((float) expected, entry.floatValue()); + assertEquals((double) expected, entry.doubleValue()); assertEquals(Long.valueOf(expected), entry.objectValue()); - + assertEquals(Long.toString(expected), entry.stringValue()); } static final void assertValue(double expected, TagMap.Entry entry) { assertEquals(expected, entry.doubleValue()); - assertEquals((int)expected, entry.intValue()); - assertEquals((long)expected, entry.longValue()); - assertEquals((float)expected, entry.floatValue()); + assertEquals((int) expected, entry.intValue()); + assertEquals((long) expected, entry.longValue()); + assertEquals((float) expected, entry.floatValue()); assertEquals(Double.valueOf(expected), entry.objectValue()); - + assertEquals(Double.toString(expected), entry.stringValue()); } - + static final void assertValue(float expected, TagMap.Entry entry) { assertEquals(expected, entry.floatValue()); - assertEquals((int)expected, entry.intValue()); - assertEquals((long)expected, entry.longValue()); - assertEquals((double)expected, entry.doubleValue()); + assertEquals((int) expected, entry.intValue()); + assertEquals((long) expected, entry.longValue()); + assertEquals((double) expected, entry.doubleValue()); assertEquals(Float.valueOf(expected), entry.objectValue()); - + assertEquals(Float.toString(expected), entry.stringValue()); } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index 053997f7bd4..91345c5c2ff 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -11,431 +11,1204 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; - import org.junit.jupiter.api.Test; public final class TagMapFuzzTest { static final int NUM_KEYS = 128; static final int MAX_NUM_ACTIONS = 32; - + @Test void test() { test(generateTest()); } - + @Test void testMerge() { TestCase mapACase = generateTest(); TestCase mapBCase = generateTest(); - + TagMap tagMapA = test(mapACase); TagMap tagMapB = test(mapBCase); - + HashMap hashMapA = new HashMap<>(tagMapA); HashMap hashMapB = new HashMap<>(tagMapB); - + tagMapA.putAll(tagMapB); hashMapA.putAll(hashMapB); - + assertMapEquals(hashMapA, tagMapA); } - + @Test void priorFailingCase0() { - TagMap map = makeTagMap( - remove("key-4"), - put("key-71","values-443049055"), - put("key-2","values-1227065898"), - put("key-25","values-696891692"), - put("key-93","values-763707175"), - put("key-23","values--1514091210"), - put("key-16","values--1388742686") - ); - - MapAction failingAction = putAllTagMap("key-17","values--2085338893","key-51","values-960243765","key-33","values-1493544499","key-46","values-697926849","key-70","values--184054454","key-67","values-374577326","key-9","values--742453833","key-11","values-1606950841","key-119","values--1914593057","key-53","values-375236438","key-96","values--107185569","key-47","values--1276407408","key-125","values--1627172151","key-110","values--1227150283","key-15","values-380379920","key-42","values--632271048","key-99","values--650090786","key-8","values--1990889145","key-103","values-1815698254","key-120","values-279025031","key-93","values-589795963","key-12","values--935895941","key-105","values-94976227","key-85","values--424609970","key-78","values-1231948102","key-115","values-88670282","key-26","values-733903384","key-100","values-2102967487","key-74","values-958598087","key-104","values-264458254","key-125","values--1781797927","key-27","values--562810078","key-7","values--376776745","key-111","values-263564677","key-50","values--859673100","key-57","values-1585057281","key-48","values--617889787","key-98","values--1878108220","key-9","values--227223375","key-59","values-1577082288","key-94","values--268049040","key-0","values-1708355496","key-62","values--733451297","key-14","values-232732747","key-4","values--406605642","key-58","values-1772476833","key-8","values--1155025225","key-101","values-144480545","key-66","values-355117269","key-121","values-1858008722","key-33","values-1947754079","key-1","values--1475603838","key-125","values--2146772243","key-117","values-852022714","key-53","values--2039348506","key-65","values-2011228657","key-108","values-1581592518","key-17","values-2129571020","key-5","values-1106900841","key-80","values-1791757923","key-18","values--1992962227","key-2","values-328863878","key-110","values-1182949334","key-5","values-1049403346","key-107","values-1246502060","key-115","values-2053931423","key-19","values--1731179633","key-104","values--1090790550","key-67","values--1312759979","key-10","values-1411135","key-109","values--1784920248","key-20","values--827644780","key-55","values--1610270998","key-60","values-1287959520","key-31","values-1686541667","key-41","values-399844058","key-115","values-2045201464","key-78","values-358081227","key-57","values--1374149269","key-65","values-1871734555","key-124","values--211494558","key-119","values-1757597102","key-32","values--336988038","key-85","values-1415155858","key-44","values-1455425178","key-48","values--325658059","key-68","values--793590840","key-96","values--2010766492","key-40","values-2007171160","key-29","values-186945230","key-63","values-1741962849","key-26","values-948582805","key-31","values-47004766","key-90","values-1304302008","key-69","values-2120328211","key-111","values-2053321468","key-69","values--498524858","key-125","values--193004619","key-30","values--1142090845","key-15","values--1334900170","key-33","values-1011001500","key-55","values-452401605","key-18","values-1260118555","key-44","values--1109396459","key-2","values--555647718","key-61","values-1060742038","key-51","values--827099230","key-62","values--1443716296","key-16","values-534556355","key-81","values--787910427","key-20","values-1429697120","key-36","values--1775988293","key-66","values-624669635","key-25","values--684183265","key-26","values-293626449","key-91","values--1212867803","key-6","values-1778251481","key-83","values-1257370908","key-92","values--1120490028","key-111","values-9646496","key-90","values-1485206899"); + TagMap map = + makeTagMap( + remove("key-4"), + put("key-71", "values-443049055"), + put("key-2", "values-1227065898"), + put("key-25", "values-696891692"), + put("key-93", "values-763707175"), + put("key-23", "values--1514091210"), + put("key-16", "values--1388742686")); + + MapAction failingAction = + putAllTagMap( + "key-17", + "values--2085338893", + "key-51", + "values-960243765", + "key-33", + "values-1493544499", + "key-46", + "values-697926849", + "key-70", + "values--184054454", + "key-67", + "values-374577326", + "key-9", + "values--742453833", + "key-11", + "values-1606950841", + "key-119", + "values--1914593057", + "key-53", + "values-375236438", + "key-96", + "values--107185569", + "key-47", + "values--1276407408", + "key-125", + "values--1627172151", + "key-110", + "values--1227150283", + "key-15", + "values-380379920", + "key-42", + "values--632271048", + "key-99", + "values--650090786", + "key-8", + "values--1990889145", + "key-103", + "values-1815698254", + "key-120", + "values-279025031", + "key-93", + "values-589795963", + "key-12", + "values--935895941", + "key-105", + "values-94976227", + "key-85", + "values--424609970", + "key-78", + "values-1231948102", + "key-115", + "values-88670282", + "key-26", + "values-733903384", + "key-100", + "values-2102967487", + "key-74", + "values-958598087", + "key-104", + "values-264458254", + "key-125", + "values--1781797927", + "key-27", + "values--562810078", + "key-7", + "values--376776745", + "key-111", + "values-263564677", + "key-50", + "values--859673100", + "key-57", + "values-1585057281", + "key-48", + "values--617889787", + "key-98", + "values--1878108220", + "key-9", + "values--227223375", + "key-59", + "values-1577082288", + "key-94", + "values--268049040", + "key-0", + "values-1708355496", + "key-62", + "values--733451297", + "key-14", + "values-232732747", + "key-4", + "values--406605642", + "key-58", + "values-1772476833", + "key-8", + "values--1155025225", + "key-101", + "values-144480545", + "key-66", + "values-355117269", + "key-121", + "values-1858008722", + "key-33", + "values-1947754079", + "key-1", + "values--1475603838", + "key-125", + "values--2146772243", + "key-117", + "values-852022714", + "key-53", + "values--2039348506", + "key-65", + "values-2011228657", + "key-108", + "values-1581592518", + "key-17", + "values-2129571020", + "key-5", + "values-1106900841", + "key-80", + "values-1791757923", + "key-18", + "values--1992962227", + "key-2", + "values-328863878", + "key-110", + "values-1182949334", + "key-5", + "values-1049403346", + "key-107", + "values-1246502060", + "key-115", + "values-2053931423", + "key-19", + "values--1731179633", + "key-104", + "values--1090790550", + "key-67", + "values--1312759979", + "key-10", + "values-1411135", + "key-109", + "values--1784920248", + "key-20", + "values--827644780", + "key-55", + "values--1610270998", + "key-60", + "values-1287959520", + "key-31", + "values-1686541667", + "key-41", + "values-399844058", + "key-115", + "values-2045201464", + "key-78", + "values-358081227", + "key-57", + "values--1374149269", + "key-65", + "values-1871734555", + "key-124", + "values--211494558", + "key-119", + "values-1757597102", + "key-32", + "values--336988038", + "key-85", + "values-1415155858", + "key-44", + "values-1455425178", + "key-48", + "values--325658059", + "key-68", + "values--793590840", + "key-96", + "values--2010766492", + "key-40", + "values-2007171160", + "key-29", + "values-186945230", + "key-63", + "values-1741962849", + "key-26", + "values-948582805", + "key-31", + "values-47004766", + "key-90", + "values-1304302008", + "key-69", + "values-2120328211", + "key-111", + "values-2053321468", + "key-69", + "values--498524858", + "key-125", + "values--193004619", + "key-30", + "values--1142090845", + "key-15", + "values--1334900170", + "key-33", + "values-1011001500", + "key-55", + "values-452401605", + "key-18", + "values-1260118555", + "key-44", + "values--1109396459", + "key-2", + "values--555647718", + "key-61", + "values-1060742038", + "key-51", + "values--827099230", + "key-62", + "values--1443716296", + "key-16", + "values-534556355", + "key-81", + "values--787910427", + "key-20", + "values-1429697120", + "key-36", + "values--1775988293", + "key-66", + "values-624669635", + "key-25", + "values--684183265", + "key-26", + "values-293626449", + "key-91", + "values--1212867803", + "key-6", + "values-1778251481", + "key-83", + "values-1257370908", + "key-92", + "values--1120490028", + "key-111", + "values-9646496", + "key-90", + "values-1485206899"); failingAction.apply(map); failingAction.verify(map); } - + @Test void priorFailingCase1() { - TagMap map = makeTagMap( - put("key-68","values--37178328"), - put("key-93","values--2093086281") - ); - - MapAction failingAction = putAllTagMap("key-36","values--1951535044","key-59","values--1045985660","key-68","values-1270827526","key-65","values-440073158","key-91","values-954365843","key-75","values-1014366449","key-117","values--1306617705","key-90","values-984567966","key-120","values--1802603599","key-56","values-319574488","key-78","values--711288173","key-103","values-694279462","key-84","values-1391260657","key-59","values--484807195","key-67","values-1675498322","key-91","values--227731796","key-105","values--1471022333","key-112","values--755617374","key-117","values--668324524","key-65","values-1165174761","key-13","values--1947081814","key-72","values-2032502631","key-106","values-256372025","key-71","values--995163162","key-92","values-972782926","key-116","values-25012447","key-23","values--979671053","key-94","values-367125724","key-48","values--2011523144","key-14","values-578926680","key-65","values-1325737627","key-89","values-1539092266","key-100","values--319629978","key-53","values-1125496255","key-2","values-1988036327","key-105","values--1333468536","key-37","values-351345678","key-4","values-683252782","key-62","values--1466612877","key-100","values-268100559","key-104","values-3517495","key-48","values--1588410835","key-42","values--180653405","key-118","values--1181647255","key-17","values-509279769","key-33","values-298668287","key-76","values-2062435628","key-18","values-287811864","key-46","values--1337930894","key-50","values-2089310564","key-24","values--1870293199","key-47","values--1155431370","key-81","values--1507929564","key-115","values-1149614815","key-57","values--334611395","key-86","values-146447703","key-107","values-938082683","key-38","values-338654203","key-40","values--376260149","key-20","values--860844060","key-20","values-2003129702","key-75","values--1787311067","key-39","values--1988768973","key-58","values--479797619","key-16","values-571033631","key-65","values--1867296166","key-56","values--2071960469","key-12","values-821930484","key-40","values--54692885","key-65","values-328817493","key-121","values-1276016318","key-33","values--2081652233","key-31","values-381335133","key-77","values-1486312656","key-48","values--1058365372","key-109","values--733344537","key-85","values-1236864082","key-35","values-2045087594","key-49","values-1990762822","key-38","values--1582706513","key-18","values--626997990","key-80","values--1995264473","key-126","values--558193472","key-83","values-415016167","key-53","values-1348674948","key-58","values-612738550","key-12","values-417676134","key-101","values--58098778","key-127","values-1658306930","key-17","values-985378289","key-68","values-686600535","key-36","values-365513638","key-87","values--1737233661","key-67","values--1840935230","key-8","values-540289596","key-11","values--2045114386","key-38","values--786598887","key-48","values-1877144385","key-5","values-65838542","key-18","values-263200779","key-120","values--1500947489","key-65","values-769990109","key-38","values-1886840000","key-29","values--48760205","key-61","values--1942966789"); + TagMap map = makeTagMap(put("key-68", "values--37178328"), put("key-93", "values--2093086281")); + + MapAction failingAction = + putAllTagMap( + "key-36", + "values--1951535044", + "key-59", + "values--1045985660", + "key-68", + "values-1270827526", + "key-65", + "values-440073158", + "key-91", + "values-954365843", + "key-75", + "values-1014366449", + "key-117", + "values--1306617705", + "key-90", + "values-984567966", + "key-120", + "values--1802603599", + "key-56", + "values-319574488", + "key-78", + "values--711288173", + "key-103", + "values-694279462", + "key-84", + "values-1391260657", + "key-59", + "values--484807195", + "key-67", + "values-1675498322", + "key-91", + "values--227731796", + "key-105", + "values--1471022333", + "key-112", + "values--755617374", + "key-117", + "values--668324524", + "key-65", + "values-1165174761", + "key-13", + "values--1947081814", + "key-72", + "values-2032502631", + "key-106", + "values-256372025", + "key-71", + "values--995163162", + "key-92", + "values-972782926", + "key-116", + "values-25012447", + "key-23", + "values--979671053", + "key-94", + "values-367125724", + "key-48", + "values--2011523144", + "key-14", + "values-578926680", + "key-65", + "values-1325737627", + "key-89", + "values-1539092266", + "key-100", + "values--319629978", + "key-53", + "values-1125496255", + "key-2", + "values-1988036327", + "key-105", + "values--1333468536", + "key-37", + "values-351345678", + "key-4", + "values-683252782", + "key-62", + "values--1466612877", + "key-100", + "values-268100559", + "key-104", + "values-3517495", + "key-48", + "values--1588410835", + "key-42", + "values--180653405", + "key-118", + "values--1181647255", + "key-17", + "values-509279769", + "key-33", + "values-298668287", + "key-76", + "values-2062435628", + "key-18", + "values-287811864", + "key-46", + "values--1337930894", + "key-50", + "values-2089310564", + "key-24", + "values--1870293199", + "key-47", + "values--1155431370", + "key-81", + "values--1507929564", + "key-115", + "values-1149614815", + "key-57", + "values--334611395", + "key-86", + "values-146447703", + "key-107", + "values-938082683", + "key-38", + "values-338654203", + "key-40", + "values--376260149", + "key-20", + "values--860844060", + "key-20", + "values-2003129702", + "key-75", + "values--1787311067", + "key-39", + "values--1988768973", + "key-58", + "values--479797619", + "key-16", + "values-571033631", + "key-65", + "values--1867296166", + "key-56", + "values--2071960469", + "key-12", + "values-821930484", + "key-40", + "values--54692885", + "key-65", + "values-328817493", + "key-121", + "values-1276016318", + "key-33", + "values--2081652233", + "key-31", + "values-381335133", + "key-77", + "values-1486312656", + "key-48", + "values--1058365372", + "key-109", + "values--733344537", + "key-85", + "values-1236864082", + "key-35", + "values-2045087594", + "key-49", + "values-1990762822", + "key-38", + "values--1582706513", + "key-18", + "values--626997990", + "key-80", + "values--1995264473", + "key-126", + "values--558193472", + "key-83", + "values-415016167", + "key-53", + "values-1348674948", + "key-58", + "values-612738550", + "key-12", + "values-417676134", + "key-101", + "values--58098778", + "key-127", + "values-1658306930", + "key-17", + "values-985378289", + "key-68", + "values-686600535", + "key-36", + "values-365513638", + "key-87", + "values--1737233661", + "key-67", + "values--1840935230", + "key-8", + "values-540289596", + "key-11", + "values--2045114386", + "key-38", + "values--786598887", + "key-48", + "values-1877144385", + "key-5", + "values-65838542", + "key-18", + "values-263200779", + "key-120", + "values--1500947489", + "key-65", + "values-769990109", + "key-38", + "values-1886840000", + "key-29", + "values--48760205", + "key-61", + "values--1942966789"); failingAction.apply(map); failingAction.verify(map); } - + @Test void priorFailingCase2() { - TestCase testCase = new TestCase( - remove("key-34"), - put("key-122","values-1828753938"), - putAll("key-123","values--118789056","key-28","values--751841781","key-105","values-1663318183","key-63","values--2036414463","key-74","values-1584612783","key-118","values--414681411","key-67","values-1154668404","key-1","values--1755856616","key-89","values--344740102","key-110","values-1884649283","key-1","values--1420345075","key-22","values-1951712698","key-103","values-488559164","key-8","values-1180668912","key-44","values-290310046","key-105","values--303926067","key-26","values-910376351","key-59","values-1600204544","key-23","values-425861746","key-76","values--1045446587","key-21","values-453905226","key-1","values-286624672","key-69","values-934359656","key-57","values--1890465763","key-13","values--1949062639","key-68","values-242077328","key-42","values--1584075743","key-46","values--1306318288","key-31","values--848418043","key-71","values--1547961101","key-121","values--1493693636","key-24","values-330660358","key-24","values--1466871690","key-91","values--995064376","key-18","values-1615316779","key-124","values--296191510","key-52","values-740309054","key-8","values-1777392898","key-73","values-92831985","key-13","values--1711360891","key-114","values-1960346620","key-44","values--1599497099","key-107","values-668485357","key-116","values--1792788504"), - put("key-123","values--1844485682"), - putAll("key-64","values--1694520036","key-17","values--469732912","key-79","values--1293521097","key-11","values--2000592955","key-98","values-517073723","key-28","values-1085152681","key-34","values-1943586726","key-3","values-216087991","key-97","values-222660872","key-41","values-90906196","key-63","values--934208984","key-57","values-327167184","key-111","values--1059115125","key-75","values--2031064209","key-8","values-1924310140","key-69","values--362514182","key-90","values-852043703","key-98","values--998302860","key-49","values-1658920804","key-106","values--227162298","key-25","values-493046373","key-52","values--555623542","key-77","values--717275660","key-31","values-1930766287","key-69","values--1367213079","key-38","values--1112081116","key-65","values--1916889923","key-96","values-157036191","key-127","values--302553995","key-38","values-485874872","key-110","values--855874569","key-39","values--390829775","key-7","values--452123269","key-63","values--527204905","key-101","values-166173307","key-126","values-1050454498","key-4","values--215188400","key-25","values-947961204","key-42","values-145803888","key-1","values--970532578","key-43","values--1675493776","key-29","values-1193328809","key-108","values-1302659140","key-120","values--1722764270","key-24","values--483238806","key-53","values-611589672","key-39","values--229429656","key-29","values--733337788","key-9","values-736222322","key-74","values--950770749","key-91","values-202817768","key-95","values-500260096","key-71","values--1798188865","key-12","values--1936098297","key-28","values--2116134632","key-21","values-799594067","key-68","values--333178107","key-50","values-445767791","key-88","values-1307699662","key-69","values--110615017","key-25","values-699603233","key-101","values--2093413536","key-91","values--2022040839","key-45","values-888546703","key-40","values--2140684954","key-1","values-371033654","key-68","values--20293415","key-59","values-697437101","key-43","values--1145022834","key-62","values--2125187195","key-15","values--1062944166","key-103","values--889634836","key-125","values-8694763","key-101","values--281475498","key-13","values-1972488719","key-32","values-1900833863","key-119","values--926978044","key-82","values-288820151","key-78","values--303310027","key-25","values--1284661437","key-47","values-1624726045","key-14","values-1658036950","key-65","values-1629683219","key-10","values-275264679","key-126","values--592085694","key-32","values-1844385705","key-85","values--1815321660","key-72","values-918231225","key-91","values-675699466","key-121","values--2008685332","key-61","values--1398921570","key-19","values-617817427","key-122","values--793708860","key-41","values--2027225350","key-41","values-1194206680","key-1","values-1116090448","key-49","values-1662444555","key-54","values-747436284","key-118","values--1367237858","key-65","values-133495093","key-73","values--1451855551","key-43","values--357794833","key-76","values-129403123","key-59","values--65688873","key-22","values-480031738","key-73","values--310815862","key-0","values--1734944386","key-56","values--540459893","key-38","values-1308912555","key-2","values--2073028093","key-14","values--693713438","key-76","values-295450436","key-113","values--2065146687","key-0","values-2076623027","key-17","values--1394046356","key-78","values--2014478659","key-5","values--665180960"), - put("key-124","values-460160716"), - put("key-112","values--1828904046"), - put("key-41","values--904162962")); - + TestCase testCase = + new TestCase( + remove("key-34"), + put("key-122", "values-1828753938"), + putAll( + "key-123", + "values--118789056", + "key-28", + "values--751841781", + "key-105", + "values-1663318183", + "key-63", + "values--2036414463", + "key-74", + "values-1584612783", + "key-118", + "values--414681411", + "key-67", + "values-1154668404", + "key-1", + "values--1755856616", + "key-89", + "values--344740102", + "key-110", + "values-1884649283", + "key-1", + "values--1420345075", + "key-22", + "values-1951712698", + "key-103", + "values-488559164", + "key-8", + "values-1180668912", + "key-44", + "values-290310046", + "key-105", + "values--303926067", + "key-26", + "values-910376351", + "key-59", + "values-1600204544", + "key-23", + "values-425861746", + "key-76", + "values--1045446587", + "key-21", + "values-453905226", + "key-1", + "values-286624672", + "key-69", + "values-934359656", + "key-57", + "values--1890465763", + "key-13", + "values--1949062639", + "key-68", + "values-242077328", + "key-42", + "values--1584075743", + "key-46", + "values--1306318288", + "key-31", + "values--848418043", + "key-71", + "values--1547961101", + "key-121", + "values--1493693636", + "key-24", + "values-330660358", + "key-24", + "values--1466871690", + "key-91", + "values--995064376", + "key-18", + "values-1615316779", + "key-124", + "values--296191510", + "key-52", + "values-740309054", + "key-8", + "values-1777392898", + "key-73", + "values-92831985", + "key-13", + "values--1711360891", + "key-114", + "values-1960346620", + "key-44", + "values--1599497099", + "key-107", + "values-668485357", + "key-116", + "values--1792788504"), + put("key-123", "values--1844485682"), + putAll( + "key-64", + "values--1694520036", + "key-17", + "values--469732912", + "key-79", + "values--1293521097", + "key-11", + "values--2000592955", + "key-98", + "values-517073723", + "key-28", + "values-1085152681", + "key-34", + "values-1943586726", + "key-3", + "values-216087991", + "key-97", + "values-222660872", + "key-41", + "values-90906196", + "key-63", + "values--934208984", + "key-57", + "values-327167184", + "key-111", + "values--1059115125", + "key-75", + "values--2031064209", + "key-8", + "values-1924310140", + "key-69", + "values--362514182", + "key-90", + "values-852043703", + "key-98", + "values--998302860", + "key-49", + "values-1658920804", + "key-106", + "values--227162298", + "key-25", + "values-493046373", + "key-52", + "values--555623542", + "key-77", + "values--717275660", + "key-31", + "values-1930766287", + "key-69", + "values--1367213079", + "key-38", + "values--1112081116", + "key-65", + "values--1916889923", + "key-96", + "values-157036191", + "key-127", + "values--302553995", + "key-38", + "values-485874872", + "key-110", + "values--855874569", + "key-39", + "values--390829775", + "key-7", + "values--452123269", + "key-63", + "values--527204905", + "key-101", + "values-166173307", + "key-126", + "values-1050454498", + "key-4", + "values--215188400", + "key-25", + "values-947961204", + "key-42", + "values-145803888", + "key-1", + "values--970532578", + "key-43", + "values--1675493776", + "key-29", + "values-1193328809", + "key-108", + "values-1302659140", + "key-120", + "values--1722764270", + "key-24", + "values--483238806", + "key-53", + "values-611589672", + "key-39", + "values--229429656", + "key-29", + "values--733337788", + "key-9", + "values-736222322", + "key-74", + "values--950770749", + "key-91", + "values-202817768", + "key-95", + "values-500260096", + "key-71", + "values--1798188865", + "key-12", + "values--1936098297", + "key-28", + "values--2116134632", + "key-21", + "values-799594067", + "key-68", + "values--333178107", + "key-50", + "values-445767791", + "key-88", + "values-1307699662", + "key-69", + "values--110615017", + "key-25", + "values-699603233", + "key-101", + "values--2093413536", + "key-91", + "values--2022040839", + "key-45", + "values-888546703", + "key-40", + "values--2140684954", + "key-1", + "values-371033654", + "key-68", + "values--20293415", + "key-59", + "values-697437101", + "key-43", + "values--1145022834", + "key-62", + "values--2125187195", + "key-15", + "values--1062944166", + "key-103", + "values--889634836", + "key-125", + "values-8694763", + "key-101", + "values--281475498", + "key-13", + "values-1972488719", + "key-32", + "values-1900833863", + "key-119", + "values--926978044", + "key-82", + "values-288820151", + "key-78", + "values--303310027", + "key-25", + "values--1284661437", + "key-47", + "values-1624726045", + "key-14", + "values-1658036950", + "key-65", + "values-1629683219", + "key-10", + "values-275264679", + "key-126", + "values--592085694", + "key-32", + "values-1844385705", + "key-85", + "values--1815321660", + "key-72", + "values-918231225", + "key-91", + "values-675699466", + "key-121", + "values--2008685332", + "key-61", + "values--1398921570", + "key-19", + "values-617817427", + "key-122", + "values--793708860", + "key-41", + "values--2027225350", + "key-41", + "values-1194206680", + "key-1", + "values-1116090448", + "key-49", + "values-1662444555", + "key-54", + "values-747436284", + "key-118", + "values--1367237858", + "key-65", + "values-133495093", + "key-73", + "values--1451855551", + "key-43", + "values--357794833", + "key-76", + "values-129403123", + "key-59", + "values--65688873", + "key-22", + "values-480031738", + "key-73", + "values--310815862", + "key-0", + "values--1734944386", + "key-56", + "values--540459893", + "key-38", + "values-1308912555", + "key-2", + "values--2073028093", + "key-14", + "values--693713438", + "key-76", + "values-295450436", + "key-113", + "values--2065146687", + "key-0", + "values-2076623027", + "key-17", + "values--1394046356", + "key-78", + "values--2014478659", + "key-5", + "values--665180960"), + put("key-124", "values-460160716"), + put("key-112", "values--1828904046"), + put("key-41", "values--904162962")); + Map expected = makeMap(testCase); TagMap actual = makeTagMap(testCase); - + MapAction failingAction = remove("key-127"); failingAction.apply(expected); failingAction.verify(expected); - + failingAction.apply(actual); failingAction.verify(actual); - + assertMapEquals(expected, actual); } - + public static final TagMap test(MapAction... actions) { return test(new TestCase(Arrays.asList(actions))); } - + public static final Map makeMap(TestCase testCase) { return makeMap(testCase.actions); } - + public static final Map makeMap(MapAction... actions) { return makeMap(Arrays.asList(actions)); } - + public static final Map makeMap(List actions) { Map map = new HashMap<>(); - for ( MapAction action: actions ) { + for (MapAction action : actions) { action.apply(map); } return map; } - + public static final TagMap makeTagMap(TestCase testCase) { return makeTagMap(testCase.actions); } - + public static final TagMap makeTagMap(MapAction... actions) { return makeTagMap(Arrays.asList(actions)); } - + public static final TagMap makeTagMap(List actions) { TagMap map = new TagMap(); - for ( MapAction action: actions ) { + for (MapAction action : actions) { action.apply(map); } return map; } - + public static final TagMap test(TestCase test) { List actions = test.actions(); - + Map hashMap = new HashMap<>(); TagMap tagMap = new TagMap(); int actionIndex = 0; try { - for ( actionIndex = 0; actionIndex < actions.size(); ++actionIndex ) { + for (actionIndex = 0; actionIndex < actions.size(); ++actionIndex) { MapAction action = actions.get(actionIndex); - + Object expected = action.apply(hashMap); Object result = action.apply(tagMap); - + assertEquals(expected, result); - + action.verify(tagMap); - + assertMapEquals(hashMap, tagMap); } - } catch ( Error e ) { + } catch (Error e) { System.err.println(new TestCase(actions.subList(0, actionIndex + 1))); - + throw e; - } + } return tagMap; } - + public static final TestCase generateTest() { return generateTest(ThreadLocalRandom.current().nextInt(MAX_NUM_ACTIONS)); } - + public static final TestCase generateTest(int size) { List actions = new ArrayList<>(size); - for ( int i = 0; i < size; ++i ) { + for (int i = 0; i < size; ++i) { actions.add(randomAction()); } return new TestCase(actions); } - + public static final MapAction randomAction() { float actionSelector = ThreadLocalRandom.current().nextFloat(); - - if ( actionSelector > 0.5 ) { + + if (actionSelector > 0.5) { // 50% puts return put(randomKey(), randomValue()); - } else if ( actionSelector > 0.3 ) { + } else if (actionSelector > 0.3) { // 20% removes return remove(randomKey()); - } else if ( actionSelector > 0.2 ) { + } else if (actionSelector > 0.2) { // 10% putAll TagMap return putAllTagMap(randomKeysAndValues()); - } else if ( actionSelector > 0.02 ) { + } else if (actionSelector > 0.02) { // ~10% putAll HashMap return putAll(randomKeysAndValues()); } else { return clear(); } } - + public static final MapAction put(String key, String value) { return new Put(key, value); } - + public static final MapAction putAll(String... keysAndValues) { return new PutAll(keysAndValues); } - + public static final MapAction putAllTagMap(String... keysAndValues) { return new PutAllTagMap(keysAndValues); } - + public static final MapAction clear() { return Clear.INSTANCE; } - + public static final MapAction remove(String key) { return new Remove(key); } - + static final void assertMapEquals(Map expected, TagMap actual) { // checks entries in both directions to make sure there's full intersection - - for ( Map.Entry expectedEntry: expected.entrySet() ) { + + for (Map.Entry expectedEntry : expected.entrySet()) { TagMap.Entry actualEntry = actual.getEntry(expectedEntry.getKey()); assertNotNull(actualEntry); assertEquals(expectedEntry.getValue(), actualEntry.getValue()); } - - for ( TagMap.Entry actualEntry: actual ) { + + for (TagMap.Entry actualEntry : actual) { Object expectedValue = expected.get(actualEntry.tag()); assertEquals(expectedValue, actualEntry.objectValue()); } } - + static final String randomKey() { return "key-" + ThreadLocalRandom.current().nextInt(NUM_KEYS); } - + static final String randomValue() { return "values-" + ThreadLocalRandom.current().nextInt(); } - + static final String[] randomKeysAndValues() { int numEntries = ThreadLocalRandom.current().nextInt(NUM_KEYS); - + String[] keysAndValues = new String[numEntries << 1]; - for ( int i = 0; i < keysAndValues.length; i += 2 ) { + for (int i = 0; i < keysAndValues.length; i += 2) { keysAndValues[i] = randomKey(); keysAndValues[i + 1] = randomValue(); } return keysAndValues; } - + static final String literal(String str) { return "\"" + str + "\""; } - + static final String literalVarArgs(String... strs) { StringBuilder builder = new StringBuilder(); - for ( String str: strs ) { - if ( builder.length() != 0 ) builder.append(','); + for (String str : strs) { + if (builder.length() != 0) builder.append(','); builder.append(literal(str)); } return builder.toString(); } - + static final Map mapOf(String... keysAndValues) { HashMap map = new HashMap<>(keysAndValues.length >> 1); - for ( int i = 0; i < keysAndValues.length; i += 2 ) { + for (int i = 0; i < keysAndValues.length; i += 2) { String key = keysAndValues[i]; String value = keysAndValues[i + 1]; - + map.put(key, value); } return map; } - + static final TagMap tagMapOf(String... keysAndValues) { TagMap map = new TagMap(); - for ( int i = 0; i < keysAndValues.length; i += 2 ) { + for (int i = 0; i < keysAndValues.length; i += 2) { String key = keysAndValues[i]; String value = keysAndValues[i + 1]; - + map.set(key, value); // map.check(); } return map; } - + static final class TestCase { final List actions; - + TestCase(MapAction... actions) { this.actions = Arrays.asList(actions); } - + TestCase(List actions) { this.actions = actions; } - + public final List actions() { return this.actions; } - + @Override public String toString() { StringBuilder builder = new StringBuilder(); - for ( MapAction action: this.actions ) { + for (MapAction action : this.actions) { builder.append(action).append(',').append('\n'); } return builder.toString(); } } - - static abstract class MapAction { + + abstract static class MapAction { public abstract Object apply(Map mapUnderTest); - + public abstract void verify(Map mapUnderTest); - + public abstract String toString(); } - + static final class Put extends MapAction { final String key; final String value; - + Put(String key, String value) { this.key = key; this.value = value; } - + @Override public Object apply(Map mapUnderTest) { return mapUnderTest.put(this.key, this.value); } - + @Override public void verify(Map mapUnderTest) { assertEquals(this.value, mapUnderTest.get(this.key)); } - + @Override public String toString() { return String.format("put(%s,%s)", literal(this.key), literal(this.value)); } } - + static final class PutAll extends MapAction { final String[] keysAndValues; final Map map; - + PutAll(String... keysAndValues) { this.keysAndValues = keysAndValues; this.map = mapOf(keysAndValues); } - + @Override public Object apply(Map mapUnderTest) { mapUnderTest.putAll(this.map); - + return void.class; } - + @Override public void verify(Map mapUnderTest) { - for ( Map.Entry entry: this.map.entrySet() ) { + for (Map.Entry entry : this.map.entrySet()) { assertEquals(entry.getValue(), mapUnderTest.get(entry.getKey())); } } - + @Override public String toString() { return String.format("putAll(%s)", literalVarArgs(this.keysAndValues)); } } - + static final class PutAllTagMap extends MapAction { final String[] keysAndValues; final TagMap tagMap; - + PutAllTagMap(String... keysAndValues) { this.keysAndValues = keysAndValues; this.tagMap = tagMapOf(keysAndValues); } - + @Override public Object apply(Map mapUnderTest) { mapUnderTest.putAll(this.tagMap); - + return void.class; } - + @Override public void verify(Map mapUnderTest) { - for ( TagMap.Entry entry: this.tagMap ) { + for (TagMap.Entry entry : this.tagMap) { assertEquals(entry.objectValue(), mapUnderTest.get(entry.tag()), "key=" + entry.tag()); } } - + @Override public String toString() { return String.format("putAllTagMap(%s)", literalVarArgs(this.keysAndValues)); } } - + static final class Remove extends MapAction { final String key; - + Remove(String key) { this.key = key; } - + @Override public Object apply(Map mapUnderTest) { return mapUnderTest.remove(this.key); } - + @Override public void verify(Map mapUnderTest) { assertFalse(mapUnderTest.containsKey(this.key)); } - + @Override public String toString() { return String.format("remove(%s)", literal(this.key)); } } - + static final class Clear extends MapAction { static final Clear INSTANCE = new Clear(); - + private Clear() {} - + @Override public Object apply(Map mapUnderTest) { mapUnderTest.clear(); - + return void.class; } - + @Override public void verify(Map mapUnderTest) { assertTrue(mapUnderTest.isEmpty()); } - + @Override public String toString() { return "clear()"; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 4be1b76dcc9..d441ae382cc 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -11,323 +11,322 @@ import java.util.Iterator; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; - import org.junit.jupiter.api.Test; public class TagMapTest { // size is chosen to make sure to stress all types of collisions in the Map static final int MANY_SIZE = 256; - + @Test public void map_put() { TagMap map = new TagMap(); - + Object prev = map.put("foo", "bar"); assertNull(prev); - + assertEntry("foo", "bar", map); - + assertSize(1, map); assertNotEmpty(map); } - + @Test public void clear() { int size = randomSize(); - + TagMap map = createTagMap(size); assertSize(size, map); assertNotEmpty(map); - + map.clear(); assertSize(0, map); assertEmpty(map); } - + @Test public void map_put_replacement() { TagMap map = new TagMap(); Object prev1 = map.put("foo", "bar"); assertNull(prev1); - + assertEntry("foo", "bar", map); assertSize(1, map); assertNotEmpty(map); - + Object prev2 = map.put("foo", "baz"); assertEquals("bar", prev2); - + assertEntry("foo", "baz", map); } - + @Test public void map_remove() { TagMap map = new TagMap(); - + Object prev1 = map.remove("foo"); assertNull(prev1); - + map.put("foo", "bar"); assertEntry("foo", "bar", map); assertSize(1, map); assertNotEmpty(map); - + Object prev2 = map.remove("foo"); assertEquals("bar", prev2); assertSize(0, map); assertEmpty(map); } - + @Test public void freeze() { TagMap map = new TagMap(); map.put("foo", "bar"); - + assertEntry("foo", "bar", map); - + map.freeze(); - - assertFrozen(() -> { - map.remove("foo"); - }); - + + assertFrozen( + () -> { + map.remove("foo"); + }); + assertEntry("foo", "bar", map); } - + @Test public void emptyMap() { TagMap map = TagMap.EMPTY; - + assertSize(0, map); assertEmpty(map); - + assertFrozen(map); } - @Test public void putMany() { int size = randomSize(); TagMap map = createTagMap(size); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), map); } - + assertNotEmpty(map); assertSize(size, map); } - + @Test public void cloneMany() { int size = randomSize(); TagMap orig = createTagMap(size); - + TagMap copy = orig.copy(); orig.clear(); // doing this to make sure that copied isn't modified - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), copy); } } - + @Test public void replaceALot() { int size = randomSize(); TagMap map = createTagMap(size); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { int index = ThreadLocalRandom.current().nextInt(size); - + map.put(key(index), altValue(index)); assertEquals(altValue(index), map.get(key(index))); } } - + @Test public void shareEntry() { TagMap orig = new TagMap(); orig.set("foo", "bar"); - + TagMap dest = new TagMap(); dest.putEntry(orig.getEntry("foo")); - + assertSame(orig.getEntry("foo"), dest.getEntry("foo")); } - + @Test public void putAll() { int size = 67; TagMap orig = createTagMap(size); - + TagMap dest = new TagMap(); - for ( int i = size - 1; i >= 0 ; --i ) { + for (int i = size - 1; i >= 0; --i) { dest.set(key(i), altValue(i)); } - + // This should clobber all the values in dest dest.putAll(orig); - + // assertSize(size, dest); - for ( int i = 0; i < size; ++i ) { + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), dest); } } - + @Test public void removeMany() { int size = randomSize(); TagMap map = createTagMap(size); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), map); } - + assertNotEmpty(map); assertSize(size, map); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { Object removedValue = map.remove(key(i)); assertEquals(value(i), removedValue); - + // not doing exhaustive size checks assertEquals(size - i - 1, map.computeSize()); } - + assertEmpty(map); } - + @Test public void iterator() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(); - for ( TagMap.Entry entry: map ) { + for (TagMap.Entry entry : map) { // makes sure that each key is visited once and only once assertTrue(keys.add(entry.tag())); } - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + @Test public void forEachConsumer() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(size); map.forEach((entry) -> keys.add(entry.tag())); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + @Test public void forEachBiConsumer() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(size); map.forEach(keys, (k, entry) -> k.add(entry.tag())); - - for ( int i = 0; i < size; ++i ) { + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + @Test public void forEachTriConsumer() { int size = randomSize(); TagMap map = createTagMap(size); - + Set keys = new HashSet<>(size); - map.forEach(keys, "hi", (k, msg, entry) -> keys.add(entry.tag())); - - for ( int i = 0; i < size; ++i ) { + map.forEach(keys, "hi", (k, msg, entry) -> keys.add(entry.tag())); + + for (int i = 0; i < size; ++i) { // make sure the key was present assertTrue(keys.remove(key(i))); } - + // no extraneous keys assertTrue(keys.isEmpty()); } - + static final int randomSize() { return ThreadLocalRandom.current().nextInt(MANY_SIZE); } - + static final TagMap createTagMap() { return createTagMap(randomSize()); } - + static final TagMap createTagMap(int size) { TagMap map = new TagMap(); - for ( int i = 0; i < size; ++i ) { + for (int i = 0; i < size; ++i) { map.set(key(i), value(i)); } return map; } - + static final String key(int i) { return "key-" + i; } - + static final String value(int i) { return "value-" + i; } - + static final String altValue(int i) { return "alt-value-" + i; } - + static final int count(Iterable iterable) { return count(iterable.iterator()); } - + static final int count(Iterator iter) { int count; - for ( count = 0; iter.hasNext(); ++count ) { + for (count = 0; iter.hasNext(); ++count) { iter.next(); } return count; } - + static final void assertEntry(String key, String value, TagMap map) { TagMap.Entry entry = map.getEntry(key); assertNotNull(entry); - + assertEquals(key, entry.tag()); assertEquals(key, entry.getKey()); - + assertEquals(value, entry.objectValue()); assertTrue(entry.isObject()); assertEquals(value, entry.getValue()); assertEquals(value, entry.stringValue()); - + assertTrue(map.containsKey(key)); assertTrue(map.keySet().contains(key)); - + assertTrue(map.containsValue(value)); assertTrue(map.values().contains(value)); } - + static final void assertSize(int size, TagMap map) { assertEquals(size, map.computeSize()); assertEquals(size, map.size()); @@ -338,32 +337,32 @@ static final void assertSize(int size, TagMap map) { assertEquals(size, count(map.keySet())); assertEquals(size, count(map.values())); } - + static final void assertNotEmpty(TagMap map) { assertFalse(map.checkIfEmpty()); assertFalse(map.isEmpty()); } - + static final void assertEmpty(TagMap map) { assertTrue(map.checkIfEmpty()); assertTrue(map.isEmpty()); } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); } - + static final void assertFrozen(Runnable runnable) { IllegalStateException ex = null; try { runnable.run(); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); From fd45d8df54df139a67b307397634ccdd3ba0c2d9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 13:22:05 -0400 Subject: [PATCH 34/84] Restoring actor.ip check Currently, this checks fails. I believe because of a problem with Mock based tests being overfit to the HashMap implementation. --- .../groovy/com/datadog/appsec/AppSecSystemSpecification.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy index 243c7bd4b20..a92960e0a13 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy @@ -79,7 +79,7 @@ class AppSecSystemSpecification extends DDSpecification { 1 * appSecReqCtx.transferCollectedEvents() >> [Stub(AppSecEvent)] 1 * appSecReqCtx.getRequestHeaders() >> ['foo-bar': ['1.1.1.1']] 1 * appSecReqCtx.getResponseHeaders() >> [:] - // 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') + 1 * traceSegment.setTagTop('actor.ip', '1.1.1.1') } void 'throws if the config file is not parseable'() { From 456ef66ea2877ed37331db33d565435c7f9a7ef1 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 14:01:22 -0400 Subject: [PATCH 35/84] Removing commented out tagsSize code --- .../src/main/java/datadog/trace/core/CoreTracer.java | 6 ------ 1 file changed, 6 deletions(-) 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 097ea534104..9cef8d7eddc 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 @@ -1753,12 +1753,6 @@ private DDSpanContext buildSpanContext() { boolean mergedTracerTagsNeedsIntercept = traceConfig.mergedTracerTagsNeedsIntercept; final int tagsSize = 0; - // final int tagsSize = - // mergedTracerTags.computeSize() - // + (null == tagBuilder ? 0 : tagBuilder.estimateSize()) - // + (null == coreTags ? 0 : coreTags.size()) - // + (null == rootSpanTags ? 0 : rootSpanTags.size()) - // + (null == contextualTags ? 0 : contextualTags.size()); if (builderRequestContextDataAppSec != null) { requestContextDataAppSec = builderRequestContextDataAppSec; From 3d89c07763858b89be2141846325b686a23bc601 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 15:08:03 -0400 Subject: [PATCH 36/84] Adding lots of Javadoc to TagMap --- .../main/java/datadog/trace/api/TagMap.java | 111 ++++++++++++++++-- 1 file changed, 102 insertions(+), 9 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index e988106f298..05a4bc6cbd8 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -32,24 +32,36 @@ public final class TagMap implements Map, Iterable { public static final TagMap EMPTY = createEmpty(); - static final TagMap createEmpty() { + private static final TagMap createEmpty() { return new TagMap().freeze(); } + /** + * Creates a new TagMap.Builder + */ public static final Builder builder() { return new Builder(); } + /** + * Creates a new TagMap.Builder which handles size modifications before expansion + */ public static final Builder builder(int size) { return new Builder(size); } + /** + * Creates a new mutable TagMap that contains the contents of map + */ public static final TagMap fromMap(Map map) { TagMap tagMap = new TagMap(); tagMap.putAll(map); return tagMap; } + /** + * Creates a new immutable TagMap that contains the contents map + */ public static final TagMap fromMapImmutable(Map map) { if (map.isEmpty()) { return TagMap.EMPTY; @@ -107,6 +119,11 @@ public boolean isEmpty() { return this.checkIfEmpty(); } + /** + * Checks if TagMap is empty + * + *

checkIfEmpty is fast but is an O(n) operation. + */ public final boolean checkIfEmpty() { Object[] thisBuckets = this.buckets; @@ -165,11 +182,17 @@ public final Object get(Object tag) { return this.getObject((String) tag); } + /** + * Provides the corresponding entry value as an Object - boxing if necessary + */ public final Object getObject(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.objectValue(); } + /** + * Provides the corresponding entry value as a String - calling toString if necessary + */ public final String getString(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.stringValue(); @@ -200,6 +223,9 @@ public final double getDouble(String tag) { return entry == null ? 0D : entry.doubleValue(); } + /** + * Provides the corresponding Entry object - preferable if the Entry needs to have its type checked + */ public final Entry getEntry(String tag) { Object[] thisBuckets = this.buckets; @@ -227,10 +253,19 @@ public final Object put(String tag, Object value) { return entry == null ? null : entry.objectValue(); } + /** + * Similar to {@link Map#put(Object, Object)}, but returns the prior Entry rather than the prior value + * + * Preferred to put because avoids having to box prior primitive value + */ public final Entry set(String tag, Object value) { return this.putEntry(Entry.newAnyEntry(tag, value)); } + /** + * Similar to {@link TagMap#set(String, Object)} but more efficient when working with CharSequences and Strings. + * Depending on this situation, this methods avoids having to do type resolution later on + */ public final Entry set(String tag, CharSequence value) { return this.putEntry(Entry.newObjectEntry(tag, value)); } @@ -255,6 +290,9 @@ public final Entry set(String tag, double value) { return this.putEntry(Entry.newDoubleEntry(tag, value)); } + /** + * TagMap specific method that places an Entry directly into the TagMap avoiding needing to allocate a new Entry object + */ public final Entry putEntry(Entry newEntry) { this.checkWriteAccess(); @@ -324,6 +362,12 @@ private final void putAll(Entry[] tagEntries, int size) { } } + /** + * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another + * + * This method takes advantage of the consistent Map layout to optimize the handling of each bucket. + * And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the source TagMap + */ public final void putAll(TagMap that) { this.checkWriteAccess(); @@ -468,6 +512,10 @@ public final Object remove(Object tag) { return entry == null ? null : entry.objectValue(); } + /** + * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior value + * This is preferred because it avoids boxing a prior primitive value + */ public final Entry removeEntry(String tag) { this.checkWriteAccess(); @@ -504,12 +552,19 @@ public final Entry removeEntry(String tag) { return null; } + /** + * Returns a mutable copy of this TagMap + */ public final TagMap copy() { TagMap copy = new TagMap(); copy.putAll(this); return copy; } + /** + * Returns an immutable copy of this TagMap + * This method is more efficient than map.copy().freeze() when called on an immutable TagMap + */ public final TagMap immutableCopy() { if (this.frozen) { return this; @@ -523,6 +578,10 @@ public final TagMap immutable() { return new TagMap(this.buckets); } + /** + * Provides an Iterator over the Entry-s of the TagMap + * Equivalent to entrySet().iterator(), but with less allocation + */ @Override public final Iterator iterator() { return new EntryIterator(this); @@ -532,6 +591,10 @@ public final Stream stream() { return StreamSupport.stream(spliterator(), false); } + /** + * Visits each Entry in this TagMap + * This method is more efficient than {@link TagMap#iterator()} + */ public final void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; @@ -550,6 +613,12 @@ public final void forEach(Consumer consumer) { } } + /** + * Version of forEach that takes an extra context object that is passed as the + * first argument to the consumer + * + * The intention is to use this method to avoid using a capturing lambda + */ public final void forEach(T thisObj, BiConsumer consumer) { Object[] thisBuckets = this.buckets; @@ -568,6 +637,12 @@ public final void forEach(T thisObj, BiConsumer con } } + /** + * Version of forEach that takes two extra context objects that are passed as the + * first two argument to the consumer + * + * The intention is to use this method to avoid using a capturing lambda + */ public final void forEach( T thisObj, U otherObj, TriConsumer consumer) { Object[] thisBuckets = this.buckets; @@ -587,21 +662,37 @@ public final void forEach( } } + /** + * Clears the TagMap + */ public final void clear() { this.checkWriteAccess(); Arrays.fill(this.buckets, null); } + /** + * Freeze the TagMap preventing further modification - returns this TagMap + */ public final TagMap freeze() { this.frozen = true; return this; } + /** + * Indicates if this map is frozen + */ public boolean isFrozen() { return this.frozen; } + + /** + * Checks if the TagMap is writable - if not throws {@link IllegalStateException} + */ + public final void checkWriteAccess() { + if (this.frozen) throw new IllegalStateException("TagMap frozen"); + } // final void check() { // Object[] thisBuckets = this.buckets; @@ -638,11 +729,14 @@ public boolean isFrozen() { // } @Override - public String toString() { - return toInternalString(); + public final String toString() { + return toPrettyString(); } - String toPrettyString() { + /** + * Standard toString implementation - output is similar to {@link java.util.HashMap#toString()} + */ + final String toPrettyString() { boolean first = true; StringBuilder builder = new StringBuilder(128); @@ -660,7 +754,10 @@ String toPrettyString() { return builder.toString(); } - String toInternalString() { + /** + * toString that more visibility into the internal structure of TagMap - primarily for deep debugging + */ + final String toInternalString() { Object[] thisBuckets = this.buckets; StringBuilder builder = new StringBuilder(128); @@ -684,10 +781,6 @@ String toInternalString() { return builder.toString(); } - public final void checkWriteAccess() { - if (this.frozen) throw new IllegalStateException("TagMap frozen"); - } - static final int _hash(String tag) { int hash = tag.hashCode(); return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); From ad2ede73c804b877bf6e5f7a7887122db466d59b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 15:26:00 -0400 Subject: [PATCH 37/84] More commenting of TagMap implementation Removed vestiges of the failed hash BloomFilter experiment --- .../main/java/datadog/trace/api/TagMap.java | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 05a4bc6cbd8..bda57dd6a91 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -876,6 +876,11 @@ static final Entry newRemovalEntry(String tag) { } final String tag; + + /* + * hash is stored in line for fast handling of Entry-s coming another Tag + * However, hash is lazily computed using the same trick as {@link java.lang.String}. + */ int hash; // To optimize construction of Entry around boxed primitives and Object entries, @@ -890,6 +895,7 @@ static final Entry newRemovalEntry(String tag) { // However, internally, it is important to remember that this type must be thread safe. // That includes multiple threads racing to resolve an ANY entry at the same time. + // Type and prim cannot use the same trick as hash because during ANY resolution the order of writes is important volatile byte type; volatile long prim; volatile Object obj; @@ -909,6 +915,8 @@ public final String tag() { } int hash() { + // If value of hash read in this thread is zero, then hash is computed. + // hash is not held as a volatile, since this computation can safely be repeated as any time int hash = this.hash; if (hash != 0) return hash; @@ -1555,9 +1563,14 @@ public Entry next() { static final class BucketGroup { static final int LEN = 4; - // int hashFilter = 0; - - // want the hashes together in the same cache line + /* + * To make search operations on BucketGroups fast, the hashes for each entry are held inside + * the BucketGroup. This avoids pointer chasing to inspect each Entry object. + * + * As a further optimization, the hashes are deliberated placed next to each other. + * The intention is that the hashes will all end up in the same cache line, so loading + * one hash effectively loads the others for free. + */ int hash0 = 0; int hash1 = 0; int hash2 = 0; @@ -1578,8 +1591,6 @@ static final class BucketGroup { this.entry0 = entry0; this.prev = prev; - - // this.hashFilter = hash0; } /** New group composed of two entries */ @@ -1589,8 +1600,6 @@ static final class BucketGroup { this.hash1 = hash1; this.entry1 = entry1; - - // this.hashFilter = hash0 | hash1; } /** New group composed of 4 entries - used for cloning */ @@ -1614,8 +1623,6 @@ static final class BucketGroup { this.hash3 = hash3; this.entry3 = entry3; - - // this.hashFilter = hash0 | hash1 | hash2 | hash3; } Entry _entryAt(int index) { @@ -1678,7 +1685,6 @@ boolean isEmptyChain() { boolean _isEmpty() { return (this.hash0 | this.hash1 | this.hash2 | this.hash3) == 0; - // return (this.hashFilter == 0); } BucketGroup findContainingGroupInChain(int hash, String tag) { @@ -1688,10 +1694,6 @@ BucketGroup findContainingGroupInChain(int hash, String tag) { return null; } - // boolean _mayContain(int hash) { - // return ((hash & this.hashFilter) == hash); - // } - Entry findInChain(int hash, String tag) { for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { Entry curEntry = curGroup._find(hash, tag); @@ -1759,28 +1761,22 @@ BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) { // so each unhandled entry from the source group is simply placed in // the same slot in the new group BucketGroup thisNewHashGroup = new BucketGroup(); - int hashFilter = 0; if (!handled0) { thisNewHashGroup.hash0 = thatCurGroup.hash0; thisNewHashGroup.entry0 = thatCurGroup.entry0; - hashFilter |= thatCurGroup.hash0; } if (!handled1) { thisNewHashGroup.hash1 = thatCurGroup.hash1; thisNewHashGroup.entry1 = thatCurGroup.entry1; - hashFilter |= thatCurGroup.hash1; } if (!handled2) { thisNewHashGroup.hash2 = thatCurGroup.hash2; thisNewHashGroup.entry2 = thatCurGroup.entry2; - hashFilter |= thatCurGroup.hash2; } if (!handled3) { thisNewHashGroup.hash3 = thatCurGroup.hash3; thisNewHashGroup.entry3 = thatCurGroup.entry3; - hashFilter |= thatCurGroup.hash3; } - // thisNewHashGroup.hashFilter = hashFilter; thisNewHashGroup.prev = thisNewestHeadGroup; thisNewestHeadGroup = thisNewHashGroup; @@ -1821,7 +1817,6 @@ Entry _replace(int hash, Entry entry) { this.entry3 = entry; } - // no need to update this.hashFilter, since the hash is already included return prevEntry; } @@ -1838,25 +1833,21 @@ boolean _insert(int hash, Entry entry) { this.hash0 = hash; this.entry0 = entry; - // this.hashFilter |= hash; inserted = true; } else if (this.hash1 == 0) { this.hash1 = hash; this.entry1 = entry; - // this.hashFilter |= hash; inserted = true; } else if (this.hash2 == 0) { this.hash2 = hash; this.entry2 = entry; - // this.hashFilter |= hash; inserted = true; } else if (this.hash3 == 0) { this.hash3 = hash; this.entry3 = entry; - // this.hashFilter |= hash; inserted = true; } return inserted; From 713be83a2a0608e40a27233e8bfa3ab582a505e1 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 19 Mar 2025 17:04:14 -0400 Subject: [PATCH 38/84] More commenting of the TagMap implementation --- .../main/java/datadog/trace/api/TagMap.java | 65 +++++++++++++++---- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index bda57dd6a91..6efbe8b9d62 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -5,6 +5,7 @@ import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; @@ -15,21 +16,56 @@ import java.util.stream.StreamSupport; /** - * A super simple hash map designed for... - fast copy from one map to another - compatibility with - * Builder idioms - building small maps as fast as possible - storing primitives without boxing - - * minimal memory footprint + * A super simple hash map designed for... + *

    + *
  • fast copy from one map to another + *
  • compatibility with Builder idioms + *
  • building small maps as fast as possible + *
  • storing primitives without boxing + *
  • minimal memory footprint + *
* *

This is mainly accomplished by using immutable entry objects that can reference an object or a * primitive. By using immutable entries, the entry objects can be shared between builders & maps * freely. * - *

This map lacks some features of a regular java.util.Map... - Entry object mutation - size - * tracking - falls back to computeSize - manipulating Map through the entrySet() or values() + *

This map lacks some features of a regular java.util.Map... + *

    + *
  • Entry object mutation + *
  • size tracking - falls back to computeSize + *
  • manipulating Map through the entrySet() or values() + *
* - *

Also lacks features designed for handling large maps... - bucket array expansion - adaptive - * collision + *

Also lacks features designed for handling large maps... + *

    + *
  • bucket array expansion + *
  • adaptive collision + *
+ */ + +/* + * For memory efficiency, TagMap uses a rather complicated bucket system. + *

+ * When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly. + *

+ * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain + * form a link list to handle collisions. + *

+ * Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries. + * But a BucketGroup is only formed when a collision occurs to keep allocation low in the common case of no collisions. + *

+ * For efficiency, BucketGroups are a fixed size, so when a BucketGroup fills up another BucketGroup is formed + * to hold the additional Entry-s. And the BucketGroup-s are connected via a linked list instead of the Entry-s. + *

+ * This does introduce some inefficiencies when Entry-s are removed. + * In the current system, given that removals are rare, BucketGroups are never consolidated. + * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be + * removed from the collision chain. */ public final class TagMap implements Map, Iterable { + /** + * Immutable empty TagMap - similar to {@link Collections#emptyMap()} + */ public static final TagMap EMPTY = createEmpty(); private static final TagMap createEmpty() { @@ -1555,10 +1591,12 @@ public Entry next() { } /** - * BucketGroup is compromise for performance over a linked list or array - linked list - would - * prevent TagEntry-s from being immutable and would limit sharing opportunities - array - - * wouldn't be able to store hashes close together - parallel arrays (one for hashes & another for - * entries) would require more allocation + * BucketGroup is compromise for performance over a linked list or array + * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities + * - arrays - wouldn't be able to store hashes close together + * - parallel arrays (one for hashes & another for entries) would require more allocation + * + * BucketGroups are */ static final class BucketGroup { static final int LEN = 4; @@ -1566,10 +1604,13 @@ static final class BucketGroup { /* * To make search operations on BucketGroups fast, the hashes for each entry are held inside * the BucketGroup. This avoids pointer chasing to inspect each Entry object. - * + *

* As a further optimization, the hashes are deliberated placed next to each other. * The intention is that the hashes will all end up in the same cache line, so loading * one hash effectively loads the others for free. + *

+ * A hash of zero indicates an available slot, the hashes passed to BucketGroup must be "adjusted" + * hashes which can never be zero. The zero handling is done by TagMap#hash. */ int hash0 = 0; int hash1 = 0; From f94ffbc2495df4c5bcf056ff7abc4100557e883a Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 11:46:08 -0400 Subject: [PATCH 39/84] Adding tests for BucketGroup First phase of adding tests for BucketGroup - in this first phase, just adding tests for directly manipulating the BucketGroup - not the BucketGroup chain --- .../trace/api/TagMapBucketGroupTest.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java new file mode 100644 index 00000000000..0c33fdfd94b --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -0,0 +1,149 @@ +package datadog.trace.api; + +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import datadog.trace.api.TagMap.BucketGroup; + +public class TagMapBucketGroupTest { + @Test + public void newGroup() { + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertEquals(firstHash, group._hashAt(0)); + assertEquals(firstEntry, group._entryAt(0)); + + assertEquals(secondEntry.hash(), group._hashAt(1)); + assertEquals(secondEntry, group._entryAt(1)); + + assertFalse(group._isEmpty()); + assertFalse(group.isEmptyChain()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + } + + @Test + public void _insert() { + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); + + TagMap.Entry newEntry = TagMap.Entry.newAnyEntry("baz", "lorem ipsum"); + int newHash = newEntry.hash(); + + assertTrue(group._insert(newHash, newEntry)); + + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry newEntry2 = TagMap.Entry.newDoubleEntry("new", 3.1415926535D); + int newHash2 = newEntry2.hash(); + + assertTrue(group._insert(newHash2, newEntry2)); + + assertContainsDirectly(newEntry2, group); + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry overflowEntry = TagMap.Entry.newDoubleEntry("overflow", 2.718281828D); + int overflowHash = overflowEntry.hash(); + assertFalse(group._insert(overflowHash, overflowEntry)); + + assertDoesntContainDirectly(overflowEntry, group); + } + + @Test + public void _replace() { + TagMap.Entry origEntry = TagMap.Entry.newIntEntry("replaceable", 0xDD06); + TagMap.Entry otherEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int origHash = origEntry.hash(); + int otherHash = otherEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); + assertContainsDirectly(origEntry, group); + assertContainsDirectly(otherEntry, group); + + TagMap.Entry replacementEntry = TagMap.Entry.newBooleanEntry("replaceable", true); + int replacementHash = replacementEntry.hash(); + assertEquals(replacementHash, origHash); + + TagMap.Entry priorEntry = group._replace(origHash, replacementEntry); + assertSame(priorEntry, origEntry); + + assertContainsDirectly(replacementEntry, group); + assertDoesntContainDirectly(priorEntry, group); + + TagMap.Entry dneEntry = TagMap.Entry.newAnyEntry("dne", "not present"); + int dneHash = dneEntry.hash(); + + assertNull(group._replace(dneHash, dneEntry)); + assertDoesntContainDirectly(dneEntry, group); + } + + @Test + public void _remove() { + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("first", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("second", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertFalse(group._isEmpty()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + assertSame(firstEntry, group._remove(firstHash, "first")); + + assertDoesntContainDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + assertFalse(group._isEmpty()); + + assertSame(secondEntry, group._remove(secondHash, "second")); + assertDoesntContainDirectly(secondEntry, group); + + assertTrue(group._isEmpty()); + } + + static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { + int hash = entry.hash(); + String tag = entry.tag(); + + assertSame(entry, group._find(hash, tag)); + + assertSame(entry, group.findInChain(hash, tag)); + assertSame(group, group.findContainingGroupInChain(hash, tag)); + } + + static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { + for ( int i = 0; i < BucketGroup.LEN; ++i ) { + assertNotSame(entry, group._entryAt(i)); + } + } +} From 99cf6c581d3abac5f05916ad8752b46f0fbe27da Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 15:43:28 -0400 Subject: [PATCH 40/84] BucketGroup chain tests Adding tests that cover modifications to the chain (linked list) of BucketGroups - basic construction - entry replacement - entry insertion - entry removal More tests to come --- .../trace/api/TagMapBucketGroupTest.java | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index 0c33fdfd94b..f94a48ee2bf 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertSame; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -129,6 +130,162 @@ public void _remove() { assertDoesntContainDirectly(secondEntry, group); assertTrue(group._isEmpty()); + } + + @Test + public void groupChaining() { + int startingIndex = 10; + BucketGroup firstGroup = fullGroup(startingIndex); + + for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + assertChainContainsTag(tag(startingIndex + offset), firstGroup); + } + + TagMap.Entry newEntry = TagMap.Entry.newObjectEntry("new", "new"); + int newHash = newEntry.hash(); + + // This is a test of the process used by TagMap#put + assertNull(firstGroup._replace(newHash, newEntry)); + assertFalse(firstGroup._insert(newHash, newEntry)); + assertDoesntContainDirectly(newEntry, firstGroup); + + BucketGroup newHeadGroup = new BucketGroup(newHash, newEntry, firstGroup); + assertContainsDirectly(newEntry, newHeadGroup); + assertSame(firstGroup, newHeadGroup.prev); + + assertChainContainsTag("new", newHeadGroup); + for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); + } + } + + @Test + public void removeInChain() { + BucketGroup firstGroup = fullGroup(10); + BucketGroup headGroup = fullGroup(20, firstGroup); + + for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + assertChainContainsTag(tag(10, offset), headGroup); + assertChainContainsTag(tag(20, offset), headGroup); + } + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + String firstRemovedTag = tag(10, 1); + int firstRemovedHash = TagMap._hash(firstRemovedTag); + + BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); + assertSame(firstContainingGroup, firstGroup); + assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); + + assertChainDoesntContainTag(firstRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 1); + + String secondRemovedTag = tag(20, 2); + int secondRemovedHash = TagMap._hash(secondRemovedTag); + + BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); + assertSame(secondContainingGroup, headGroup); + assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); + + assertChainDoesntContainTag(secondRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + } + + @Test + public void replaceInChain() { + BucketGroup firstGroup = fullGroup(10); + BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); + assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); + assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + } + + @Test + public void insertInChain() { + // set-up a chain with some gaps in it + BucketGroup firstGroup = fullGroup(10); + BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + + String firstHoleTag = tag(10, 1); + int firstHoleHash = TagMap._hash(firstHoleTag); + firstGroup._remove(firstHoleHash, firstHoleTag); + + String secondHoleTag = tag(20, 2); + int secondHoleHash = TagMap._hash(secondHoleTag); + headGroup._remove(secondHoleHash, secondHoleTag); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + + String firstNewTag = "new-tag-0"; + TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); + int firstNewHash = firstNewEntry.hash(); + + assertTrue(headGroup.insertInChain(firstNewHash, firstNewEntry)); + assertChainContainsTag(firstNewTag, headGroup); + + String secondNewTag = "new-tag-1"; + TagMap.Entry secondNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int secondNewHash = secondNewEntry.hash(); + + assertTrue(headGroup.insertInChain(secondNewHash, secondNewEntry)); + assertChainContainsTag(secondNewTag, headGroup); + + String thirdNewTag = "new-tag-2"; + TagMap.Entry thirdNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int thirdNewHash = secondNewEntry.hash(); + + assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); + assertChainDoesntContainTag(thirdNewTag, headGroup); + + assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + } + + static final BucketGroup fullGroup(int startingIndex) { + TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); + + BucketGroup group = new BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + for ( int offset = 2; offset < BucketGroup.LEN; ++offset ) { + TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); + group._insert(anotherEntry.hash(), anotherEntry); + } + return group; + } + + static final BucketGroup fullGroup(int startingIndex, BucketGroup prev) { + BucketGroup group = fullGroup(startingIndex); + group.prev = prev; + return group; + } + + static final String tag(int startingIndex, int offset) { + return tag(startingIndex + offset); + } + + static final String tag(int i) { + return "tag-" + i; + } + + static final String value(int startingIndex, int offset) { + return value(startingIndex + offset); + } + + static final String value(int i) { + return "value-i"; } static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { @@ -146,4 +303,14 @@ static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup g assertNotSame(entry, group._entryAt(i)); } } + + static void assertChainContainsTag(String tag, TagMap.BucketGroup group) { + int hash = TagMap._hash(tag); + assertNotNull(group.findInChain(hash, tag)); + } + + static void assertChainDoesntContainTag(String tag, TagMap.BucketGroup group) { + int hash = TagMap._hash(tag); + assertNull(group.findInChain(hash, tag)); + } } From 6d487ec1a3fa122551558c20eebe5519702777c9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 16:27:37 -0400 Subject: [PATCH 41/84] More BucketGroup tests Checking group removal from chain --- .../trace/api/TagMapBucketGroupTest.java | 113 +++++++++++++----- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index f94a48ee2bf..6d5dcc4b295 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -10,8 +10,6 @@ import org.junit.jupiter.api.Test; -import datadog.trace.api.TagMap.BucketGroup; - public class TagMapBucketGroupTest { @Test public void newGroup() { @@ -135,9 +133,9 @@ public void _remove() { @Test public void groupChaining() { int startingIndex = 10; - BucketGroup firstGroup = fullGroup(startingIndex); + TagMap.BucketGroup firstGroup = fullGroup(startingIndex); - for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { assertChainContainsTag(tag(startingIndex + offset), firstGroup); } @@ -149,76 +147,76 @@ public void groupChaining() { assertFalse(firstGroup._insert(newHash, newEntry)); assertDoesntContainDirectly(newEntry, firstGroup); - BucketGroup newHeadGroup = new BucketGroup(newHash, newEntry, firstGroup); + TagMap.BucketGroup newHeadGroup = new TagMap.BucketGroup(newHash, newEntry, firstGroup); assertContainsDirectly(newEntry, newHeadGroup); assertSame(firstGroup, newHeadGroup.prev); assertChainContainsTag("new", newHeadGroup); - for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); } } @Test public void removeInChain() { - BucketGroup firstGroup = fullGroup(10); - BucketGroup headGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - for ( int offset = 0; offset < BucketGroup.LEN; ++offset ) { + for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { assertChainContainsTag(tag(10, offset), headGroup); assertChainContainsTag(tag(20, offset), headGroup); } - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); String firstRemovedTag = tag(10, 1); int firstRemovedHash = TagMap._hash(firstRemovedTag); - BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); + TagMap.BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); assertSame(firstContainingGroup, firstGroup); assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); assertChainDoesntContainTag(firstRemovedTag, headGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 1); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 1); String secondRemovedTag = tag(20, 2); int secondRemovedHash = TagMap._hash(secondRemovedTag); - BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); + TagMap.BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); assertSame(secondContainingGroup, headGroup); assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); assertChainDoesntContainTag(secondRemovedTag, headGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); } @Test public void replaceInChain() { - BucketGroup firstGroup = fullGroup(10); - BucketGroup headGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); } @Test public void insertInChain() { // set-up a chain with some gaps in it - BucketGroup firstGroup = fullGroup(10); - BucketGroup headGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); String firstHoleTag = tag(10, 1); int firstHoleHash = TagMap._hash(firstHoleTag); @@ -228,7 +226,7 @@ public void insertInChain() { int secondHoleHash = TagMap._hash(secondHoleTag); headGroup._remove(secondHoleHash, secondHoleTag); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2 - 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); String firstNewTag = "new-tag-0"; TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); @@ -251,23 +249,62 @@ public void insertInChain() { assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); assertChainDoesntContainTag(thirdNewTag, headGroup); - assertEquals(headGroup.sizeInChain(), BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + } + + @Test + public void cloneChain() { + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup headGroup = fullGroup(30, secondGroup); + + TagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); + TagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; + TagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; + + assertGroupContentsStrictEquals(headGroup, clonedHeadGroup); + assertGroupContentsStrictEquals(secondGroup, clonedSecondGroup); + assertGroupContentsStrictEquals(firstGroup, clonedFirstGroup); + } + + @Test + public void removeGroupInChain() { + TagMap.BucketGroup tailGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); + TagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); + TagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); + TagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); + assertChain(headGroup, fourthGroup, thirdGroup, secondGroup, tailGroup); + + // need to test group removal - at head, middle, and tail of the chain + + // middle + assertSame(headGroup, headGroup.removeGroupInChain(thirdGroup)); + assertChain(headGroup, fourthGroup, secondGroup, tailGroup); + + // tail + assertSame(headGroup, headGroup.removeGroupInChain(tailGroup)); + assertChain(headGroup, fourthGroup, secondGroup); + + // head + assertSame(fourthGroup, headGroup.removeGroupInChain(headGroup)); + assertChain(fourthGroup, secondGroup); } - static final BucketGroup fullGroup(int startingIndex) { + static final TagMap.BucketGroup fullGroup(int startingIndex) { TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); - BucketGroup group = new BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); - for ( int offset = 2; offset < BucketGroup.LEN; ++offset ) { + TagMap.BucketGroup group = new TagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + for ( int offset = 2; offset < TagMap.BucketGroup.LEN; ++offset ) { TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); group._insert(anotherEntry.hash(), anotherEntry); } return group; } - static final BucketGroup fullGroup(int startingIndex, BucketGroup prev) { - BucketGroup group = fullGroup(startingIndex); + static final TagMap.BucketGroup fullGroup(int startingIndex, TagMap.BucketGroup prev) { + TagMap.BucketGroup group = fullGroup(startingIndex); group.prev = prev; return group; } @@ -299,7 +336,7 @@ static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) } static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { - for ( int i = 0; i < BucketGroup.LEN; ++i ) { + for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { assertNotSame(entry, group._entryAt(i)); } } @@ -313,4 +350,20 @@ static void assertChainDoesntContainTag(String tag, TagMap.BucketGroup group) { int hash = TagMap._hash(tag); assertNull(group.findInChain(hash, tag)); } + + static void assertGroupContentsStrictEquals(TagMap.BucketGroup expected, TagMap.BucketGroup actual) { + for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { + assertEquals(expected._hashAt(i), actual._hashAt(i)); + assertSame(expected._entryAt(i), actual._entryAt(i)); + } + } + + static void assertChain(TagMap.BucketGroup... chain) { + TagMap.BucketGroup cur; + int index; + for ( cur = chain[0], index = 0; cur != null; cur = cur.prev, ++index ) { + assertSame(chain[index], cur); + } + assertEquals(chain.length, index); + } } From 50c367fe5b9368832c2822804d932e64d75aee9b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 16:32:36 -0400 Subject: [PATCH 42/84] spotless & doc clean-up --- .../main/java/datadog/trace/api/TagMap.java | 169 +++-- .../trace/api/TagMapBucketGroupTest.java | 607 +++++++++--------- 2 files changed, 386 insertions(+), 390 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 6efbe8b9d62..0911189fdb1 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -17,29 +17,32 @@ /** * A super simple hash map designed for... + * *

    - *
  • fast copy from one map to another - *
  • compatibility with Builder idioms - *
  • building small maps as fast as possible - *
  • storing primitives without boxing - *
  • minimal memory footprint + *
  • fast copy from one map to another + *
  • compatibility with Builder idioms + *
  • building small maps as fast as possible + *
  • storing primitives without boxing + *
  • minimal memory footprint *
* *

This is mainly accomplished by using immutable entry objects that can reference an object or a * primitive. By using immutable entries, the entry objects can be shared between builders & maps * freely. * - *

This map lacks some features of a regular java.util.Map... + *

This map lacks some features of a regular java.util.Map... + * *

    - *
  • Entry object mutation - *
  • size tracking - falls back to computeSize - *
  • manipulating Map through the entrySet() or values() + *
  • Entry object mutation + *
  • size tracking - falls back to computeSize + *
  • manipulating Map through the entrySet() or values() *
* *

Also lacks features designed for handling large maps... + * *

    - *
  • bucket array expansion - *
  • adaptive collision + *
  • bucket array expansion + *
  • adaptive collision *
*/ @@ -48,56 +51,46 @@ *

* When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly. *

- * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain + * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain * form a link list to handle collisions. *

* Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries. * But a BucketGroup is only formed when a collision occurs to keep allocation low in the common case of no collisions. *

- * For efficiency, BucketGroups are a fixed size, so when a BucketGroup fills up another BucketGroup is formed + * For efficiency, BucketGroups are a fixed size, so when a BucketGroup fills up another BucketGroup is formed * to hold the additional Entry-s. And the BucketGroup-s are connected via a linked list instead of the Entry-s. *

* This does introduce some inefficiencies when Entry-s are removed. * In the current system, given that removals are rare, BucketGroups are never consolidated. - * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be + * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be * removed from the collision chain. */ public final class TagMap implements Map, Iterable { - /** - * Immutable empty TagMap - similar to {@link Collections#emptyMap()} - */ + /** Immutable empty TagMap - similar to {@link Collections#emptyMap()} */ public static final TagMap EMPTY = createEmpty(); private static final TagMap createEmpty() { return new TagMap().freeze(); } - /** - * Creates a new TagMap.Builder - */ + /** Creates a new TagMap.Builder */ public static final Builder builder() { return new Builder(); } - /** - * Creates a new TagMap.Builder which handles size modifications before expansion - */ + /** Creates a new TagMap.Builder which handles size modifications before expansion */ public static final Builder builder(int size) { return new Builder(size); } - /** - * Creates a new mutable TagMap that contains the contents of map - */ + /** Creates a new mutable TagMap that contains the contents of map */ public static final TagMap fromMap(Map map) { TagMap tagMap = new TagMap(); tagMap.putAll(map); return tagMap; } - /** - * Creates a new immutable TagMap that contains the contents map - */ + /** Creates a new immutable TagMap that contains the contents map */ public static final TagMap fromMapImmutable(Map map) { if (map.isEmpty()) { return TagMap.EMPTY; @@ -218,17 +211,13 @@ public final Object get(Object tag) { return this.getObject((String) tag); } - /** - * Provides the corresponding entry value as an Object - boxing if necessary - */ + /** Provides the corresponding entry value as an Object - boxing if necessary */ public final Object getObject(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.objectValue(); } - /** - * Provides the corresponding entry value as a String - calling toString if necessary - */ + /** Provides the corresponding entry value as a String - calling toString if necessary */ public final String getString(String tag) { Entry entry = this.getEntry(tag); return entry == null ? null : entry.stringValue(); @@ -260,7 +249,8 @@ public final double getDouble(String tag) { } /** - * Provides the corresponding Entry object - preferable if the Entry needs to have its type checked + * Provides the corresponding Entry object - preferable if the Entry needs to have its type + * checked */ public final Entry getEntry(String tag) { Object[] thisBuckets = this.buckets; @@ -290,17 +280,19 @@ public final Object put(String tag, Object value) { } /** - * Similar to {@link Map#put(Object, Object)}, but returns the prior Entry rather than the prior value - * - * Preferred to put because avoids having to box prior primitive value + * Similar to {@link Map#put(Object, Object)}, but returns the prior Entry rather than the prior + * value + * + *

Preferred to put because avoids having to box prior primitive value */ public final Entry set(String tag, Object value) { return this.putEntry(Entry.newAnyEntry(tag, value)); } /** - * Similar to {@link TagMap#set(String, Object)} but more efficient when working with CharSequences and Strings. - * Depending on this situation, this methods avoids having to do type resolution later on + * Similar to {@link TagMap#set(String, Object)} but more efficient when working with + * CharSequences and Strings. Depending on this situation, this methods avoids having to do type + * resolution later on */ public final Entry set(String tag, CharSequence value) { return this.putEntry(Entry.newObjectEntry(tag, value)); @@ -327,7 +319,8 @@ public final Entry set(String tag, double value) { } /** - * TagMap specific method that places an Entry directly into the TagMap avoiding needing to allocate a new Entry object + * TagMap specific method that places an Entry directly into the TagMap avoiding needing to + * allocate a new Entry object */ public final Entry putEntry(Entry newEntry) { this.checkWriteAccess(); @@ -400,9 +393,10 @@ private final void putAll(Entry[] tagEntries, int size) { /** * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another - * - * This method takes advantage of the consistent Map layout to optimize the handling of each bucket. - * And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the source TagMap + * + *

This method takes advantage of the consistent Map layout to optimize the handling of each + * bucket. And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the + * source TagMap */ public final void putAll(TagMap that) { this.checkWriteAccess(); @@ -549,8 +543,8 @@ public final Object remove(Object tag) { } /** - * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior value - * This is preferred because it avoids boxing a prior primitive value + * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior + * value This is preferred because it avoids boxing a prior primitive value */ public final Entry removeEntry(String tag) { this.checkWriteAccess(); @@ -588,9 +582,7 @@ public final Entry removeEntry(String tag) { return null; } - /** - * Returns a mutable copy of this TagMap - */ + /** Returns a mutable copy of this TagMap */ public final TagMap copy() { TagMap copy = new TagMap(); copy.putAll(this); @@ -598,8 +590,8 @@ public final TagMap copy() { } /** - * Returns an immutable copy of this TagMap - * This method is more efficient than map.copy().freeze() when called on an immutable TagMap + * Returns an immutable copy of this TagMap This method is more efficient than + * map.copy().freeze() when called on an immutable TagMap */ public final TagMap immutableCopy() { if (this.frozen) { @@ -615,8 +607,8 @@ public final TagMap immutable() { } /** - * Provides an Iterator over the Entry-s of the TagMap - * Equivalent to entrySet().iterator(), but with less allocation + * Provides an Iterator over the Entry-s of the TagMap Equivalent to entrySet().iterator() + * , but with less allocation */ @Override public final Iterator iterator() { @@ -628,8 +620,7 @@ public final Stream stream() { } /** - * Visits each Entry in this TagMap - * This method is more efficient than {@link TagMap#iterator()} + * Visits each Entry in this TagMap This method is more efficient than {@link TagMap#iterator()} */ public final void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; @@ -650,10 +641,10 @@ public final void forEach(Consumer consumer) { } /** - * Version of forEach that takes an extra context object that is passed as the - * first argument to the consumer - * - * The intention is to use this method to avoid using a capturing lambda + * Version of forEach that takes an extra context object that is passed as the first argument to + * the consumer + * + *

The intention is to use this method to avoid using a capturing lambda */ public final void forEach(T thisObj, BiConsumer consumer) { Object[] thisBuckets = this.buckets; @@ -674,10 +665,10 @@ public final void forEach(T thisObj, BiConsumer con } /** - * Version of forEach that takes two extra context objects that are passed as the - * first two argument to the consumer - * - * The intention is to use this method to avoid using a capturing lambda + * Version of forEach that takes two extra context objects that are passed as the first two + * argument to the consumer + * + *

The intention is to use this method to avoid using a capturing lambda */ public final void forEach( T thisObj, U otherObj, TriConsumer consumer) { @@ -698,34 +689,26 @@ public final void forEach( } } - /** - * Clears the TagMap - */ + /** Clears the TagMap */ public final void clear() { this.checkWriteAccess(); Arrays.fill(this.buckets, null); } - /** - * Freeze the TagMap preventing further modification - returns this TagMap - */ + /** Freeze the TagMap preventing further modification - returns this TagMap */ public final TagMap freeze() { this.frozen = true; return this; } - /** - * Indicates if this map is frozen - */ + /** Indicates if this map is frozen */ public boolean isFrozen() { return this.frozen; } - - /** - * Checks if the TagMap is writable - if not throws {@link IllegalStateException} - */ + + /** Checks if the TagMap is writable - if not throws {@link IllegalStateException} */ public final void checkWriteAccess() { if (this.frozen) throw new IllegalStateException("TagMap frozen"); } @@ -791,7 +774,8 @@ final String toPrettyString() { } /** - * toString that more visibility into the internal structure of TagMap - primarily for deep debugging + * toString that more visibility into the internal structure of TagMap - primarily for deep + * debugging */ final String toInternalString() { Object[] thisBuckets = this.buckets; @@ -912,7 +896,7 @@ static final Entry newRemovalEntry(String tag) { } final String tag; - + /* * hash is stored in line for fast handling of Entry-s coming another Tag * However, hash is lazily computed using the same trick as {@link java.lang.String}. @@ -931,7 +915,8 @@ static final Entry newRemovalEntry(String tag) { // However, internally, it is important to remember that this type must be thread safe. // That includes multiple threads racing to resolve an ANY entry at the same time. - // Type and prim cannot use the same trick as hash because during ANY resolution the order of writes is important + // Type and prim cannot use the same trick as hash because during ANY resolution the order of + // writes is important volatile byte type; volatile long prim; volatile Object obj; @@ -1591,26 +1576,28 @@ public Entry next() { } /** - * BucketGroup is compromise for performance over a linked list or array - * - linked list - would prevent TagEntry-s from being immutable and would limit sharing opportunities - * - arrays - wouldn't be able to store hashes close together - * - parallel arrays (one for hashes & another for entries) would require more allocation - * - * BucketGroups are + * BucketGroup is a compromise for performance over a linked list or array + * + *

    + *
  • linked list - would prevent TagEntry-s from being immutable and would limit sharing + * opportunities + *
  • arrays - wouldn't be able to store hashes close together + *
  • parallel arrays (one for hashes & another for entries) would require more allocation + *
*/ static final class BucketGroup { static final int LEN = 4; /* - * To make search operations on BucketGroups fast, the hashes for each entry are held inside + * To make search operations on BucketGroups fast, the hashes for each entry are held inside * the BucketGroup. This avoids pointer chasing to inspect each Entry object. *

- * As a further optimization, the hashes are deliberated placed next to each other. - * The intention is that the hashes will all end up in the same cache line, so loading + * As a further optimization, the hashes are deliberately placed next to each other. + * The intention is that the hashes will all end up in the same cache line, so loading * one hash effectively loads the others for free. *

- * A hash of zero indicates an available slot, the hashes passed to BucketGroup must be "adjusted" - * hashes which can never be zero. The zero handling is done by TagMap#hash. + * A hash of zero indicates an available slot, the hashes passed to BucketGroup must be "adjusted" + * hashes which can never be zero. The zero handling is done by TagMap#_hash. */ int hash0 = 0; int hash1 = 0; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index 6d5dcc4b295..cad674ffeca 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -13,357 +13,366 @@ public class TagMapBucketGroupTest { @Test public void newGroup() { - TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); - - int firstHash = firstEntry.hash(); - int secondHash = secondEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup( - firstHash, firstEntry, - secondHash, secondEntry); - - assertEquals(firstHash, group._hashAt(0)); - assertEquals(firstEntry, group._entryAt(0)); - - assertEquals(secondEntry.hash(), group._hashAt(1)); - assertEquals(secondEntry, group._entryAt(1)); - - assertFalse(group._isEmpty()); - assertFalse(group.isEmptyChain()); - - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = + new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertEquals(firstHash, group._hashAt(0)); + assertEquals(firstEntry, group._entryAt(0)); + + assertEquals(secondEntry.hash(), group._hashAt(1)); + assertEquals(secondEntry, group._entryAt(1)); + + assertFalse(group._isEmpty()); + assertFalse(group.isEmptyChain()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); } - + @Test public void _insert() { - TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); - - int firstHash = firstEntry.hash(); - int secondHash = secondEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); - - TagMap.Entry newEntry = TagMap.Entry.newAnyEntry("baz", "lorem ipsum"); - int newHash = newEntry.hash(); - - assertTrue(group._insert(newHash, newEntry)); - - assertContainsDirectly(newEntry, group); - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - - TagMap.Entry newEntry2 = TagMap.Entry.newDoubleEntry("new", 3.1415926535D); - int newHash2 = newEntry2.hash(); - - assertTrue(group._insert(newHash2, newEntry2)); - - assertContainsDirectly(newEntry2, group); - assertContainsDirectly(newEntry, group); - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - - TagMap.Entry overflowEntry = TagMap.Entry.newDoubleEntry("overflow", 2.718281828D); - int overflowHash = overflowEntry.hash(); - assertFalse(group._insert(overflowHash, overflowEntry)); - - assertDoesntContainDirectly(overflowEntry, group); + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("foo", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = + new TagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); + + TagMap.Entry newEntry = TagMap.Entry.newAnyEntry("baz", "lorem ipsum"); + int newHash = newEntry.hash(); + + assertTrue(group._insert(newHash, newEntry)); + + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry newEntry2 = TagMap.Entry.newDoubleEntry("new", 3.1415926535D); + int newHash2 = newEntry2.hash(); + + assertTrue(group._insert(newHash2, newEntry2)); + + assertContainsDirectly(newEntry2, group); + assertContainsDirectly(newEntry, group); + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + TagMap.Entry overflowEntry = TagMap.Entry.newDoubleEntry("overflow", 2.718281828D); + int overflowHash = overflowEntry.hash(); + assertFalse(group._insert(overflowHash, overflowEntry)); + + assertDoesntContainDirectly(overflowEntry, group); } - + @Test public void _replace() { - TagMap.Entry origEntry = TagMap.Entry.newIntEntry("replaceable", 0xDD06); - TagMap.Entry otherEntry = TagMap.Entry.newObjectEntry("bar", "quux"); - - int origHash = origEntry.hash(); - int otherHash = otherEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); - assertContainsDirectly(origEntry, group); - assertContainsDirectly(otherEntry, group); - - TagMap.Entry replacementEntry = TagMap.Entry.newBooleanEntry("replaceable", true); - int replacementHash = replacementEntry.hash(); - assertEquals(replacementHash, origHash); - - TagMap.Entry priorEntry = group._replace(origHash, replacementEntry); - assertSame(priorEntry, origEntry); - - assertContainsDirectly(replacementEntry, group); - assertDoesntContainDirectly(priorEntry, group); - - TagMap.Entry dneEntry = TagMap.Entry.newAnyEntry("dne", "not present"); - int dneHash = dneEntry.hash(); - - assertNull(group._replace(dneHash, dneEntry)); - assertDoesntContainDirectly(dneEntry, group); + TagMap.Entry origEntry = TagMap.Entry.newIntEntry("replaceable", 0xDD06); + TagMap.Entry otherEntry = TagMap.Entry.newObjectEntry("bar", "quux"); + + int origHash = origEntry.hash(); + int otherHash = otherEntry.hash(); + + TagMap.BucketGroup group = new TagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); + assertContainsDirectly(origEntry, group); + assertContainsDirectly(otherEntry, group); + + TagMap.Entry replacementEntry = TagMap.Entry.newBooleanEntry("replaceable", true); + int replacementHash = replacementEntry.hash(); + assertEquals(replacementHash, origHash); + + TagMap.Entry priorEntry = group._replace(origHash, replacementEntry); + assertSame(priorEntry, origEntry); + + assertContainsDirectly(replacementEntry, group); + assertDoesntContainDirectly(priorEntry, group); + + TagMap.Entry dneEntry = TagMap.Entry.newAnyEntry("dne", "not present"); + int dneHash = dneEntry.hash(); + + assertNull(group._replace(dneHash, dneEntry)); + assertDoesntContainDirectly(dneEntry, group); } - + @Test public void _remove() { - TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("first", 0xDD06); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("second", "quux"); - - int firstHash = firstEntry.hash(); - int secondHash = secondEntry.hash(); - - TagMap.BucketGroup group = new TagMap.BucketGroup( - firstHash, firstEntry, - secondHash, secondEntry); - - assertFalse(group._isEmpty()); - - assertContainsDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - - assertSame(firstEntry, group._remove(firstHash, "first")); - - assertDoesntContainDirectly(firstEntry, group); - assertContainsDirectly(secondEntry, group); - assertFalse(group._isEmpty()); - - assertSame(secondEntry, group._remove(secondHash, "second")); - assertDoesntContainDirectly(secondEntry, group); - - assertTrue(group._isEmpty()); - } - + TagMap.Entry firstEntry = TagMap.Entry.newIntEntry("first", 0xDD06); + TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry("second", "quux"); + + int firstHash = firstEntry.hash(); + int secondHash = secondEntry.hash(); + + TagMap.BucketGroup group = + new TagMap.BucketGroup( + firstHash, firstEntry, + secondHash, secondEntry); + + assertFalse(group._isEmpty()); + + assertContainsDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + + assertSame(firstEntry, group._remove(firstHash, "first")); + + assertDoesntContainDirectly(firstEntry, group); + assertContainsDirectly(secondEntry, group); + assertFalse(group._isEmpty()); + + assertSame(secondEntry, group._remove(secondHash, "second")); + assertDoesntContainDirectly(secondEntry, group); + + assertTrue(group._isEmpty()); + } + @Test public void groupChaining() { - int startingIndex = 10; - TagMap.BucketGroup firstGroup = fullGroup(startingIndex); - - for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { - assertChainContainsTag(tag(startingIndex + offset), firstGroup); - } - - TagMap.Entry newEntry = TagMap.Entry.newObjectEntry("new", "new"); - int newHash = newEntry.hash(); - - // This is a test of the process used by TagMap#put - assertNull(firstGroup._replace(newHash, newEntry)); - assertFalse(firstGroup._insert(newHash, newEntry)); - assertDoesntContainDirectly(newEntry, firstGroup); - - TagMap.BucketGroup newHeadGroup = new TagMap.BucketGroup(newHash, newEntry, firstGroup); - assertContainsDirectly(newEntry, newHeadGroup); - assertSame(firstGroup, newHeadGroup.prev); - - assertChainContainsTag("new", newHeadGroup); - for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { - assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); - } + int startingIndex = 10; + TagMap.BucketGroup firstGroup = fullGroup(startingIndex); + + for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + assertChainContainsTag(tag(startingIndex + offset), firstGroup); + } + + TagMap.Entry newEntry = TagMap.Entry.newObjectEntry("new", "new"); + int newHash = newEntry.hash(); + + // This is a test of the process used by TagMap#put + assertNull(firstGroup._replace(newHash, newEntry)); + assertFalse(firstGroup._insert(newHash, newEntry)); + assertDoesntContainDirectly(newEntry, firstGroup); + + TagMap.BucketGroup newHeadGroup = new TagMap.BucketGroup(newHash, newEntry, firstGroup); + assertContainsDirectly(newEntry, newHeadGroup); + assertSame(firstGroup, newHeadGroup.prev); + + assertChainContainsTag("new", newHeadGroup); + for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); + } } - + @Test public void removeInChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - - for ( int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset ) { - assertChainContainsTag(tag(10, offset), headGroup); - assertChainContainsTag(tag(20, offset), headGroup); - } - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - String firstRemovedTag = tag(10, 1); - int firstRemovedHash = TagMap._hash(firstRemovedTag); - - TagMap.BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); - assertSame(firstContainingGroup, firstGroup); - assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); - - assertChainDoesntContainTag(firstRemovedTag, headGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 1); - - String secondRemovedTag = tag(20, 2); - int secondRemovedHash = TagMap._hash(secondRemovedTag); - - TagMap.BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); - assertSame(secondContainingGroup, headGroup); - assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); - - assertChainDoesntContainTag(secondRemovedTag, headGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + + for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + assertChainContainsTag(tag(10, offset), headGroup); + assertChainContainsTag(tag(20, offset), headGroup); + } + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + String firstRemovedTag = tag(10, 1); + int firstRemovedHash = TagMap._hash(firstRemovedTag); + + TagMap.BucketGroup firstContainingGroup = + headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); + assertSame(firstContainingGroup, firstGroup); + assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); + + assertChainDoesntContainTag(firstRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 1); + + String secondRemovedTag = tag(20, 2); + int secondRemovedHash = TagMap._hash(secondRemovedTag); + + TagMap.BucketGroup secondContainingGroup = + headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); + assertSame(secondContainingGroup, headGroup); + assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); + + assertChainDoesntContainTag(secondRemovedTag, headGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); } - + @Test public void replaceInChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); - assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); - assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); + assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); + assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); } - + @Test public void insertInChain() { - // set-up a chain with some gaps in it - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); - - String firstHoleTag = tag(10, 1); - int firstHoleHash = TagMap._hash(firstHoleTag); - firstGroup._remove(firstHoleHash, firstHoleTag); - - String secondHoleTag = tag(20, 2); - int secondHoleHash = TagMap._hash(secondHoleTag); - headGroup._remove(secondHoleHash, secondHoleTag); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); - - String firstNewTag = "new-tag-0"; - TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); - int firstNewHash = firstNewEntry.hash(); - - assertTrue(headGroup.insertInChain(firstNewHash, firstNewEntry)); - assertChainContainsTag(firstNewTag, headGroup); - - String secondNewTag = "new-tag-1"; - TagMap.Entry secondNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); - int secondNewHash = secondNewEntry.hash(); - - assertTrue(headGroup.insertInChain(secondNewHash, secondNewEntry)); - assertChainContainsTag(secondNewTag, headGroup); - - String thirdNewTag = "new-tag-2"; - TagMap.Entry thirdNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); - int thirdNewHash = secondNewEntry.hash(); - - assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); - assertChainDoesntContainTag(thirdNewTag, headGroup); - - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + // set-up a chain with some gaps in it + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + + String firstHoleTag = tag(10, 1); + int firstHoleHash = TagMap._hash(firstHoleTag); + firstGroup._remove(firstHoleHash, firstHoleTag); + + String secondHoleTag = tag(20, 2); + int secondHoleHash = TagMap._hash(secondHoleTag); + headGroup._remove(secondHoleHash, secondHoleTag); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); + + String firstNewTag = "new-tag-0"; + TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); + int firstNewHash = firstNewEntry.hash(); + + assertTrue(headGroup.insertInChain(firstNewHash, firstNewEntry)); + assertChainContainsTag(firstNewTag, headGroup); + + String secondNewTag = "new-tag-1"; + TagMap.Entry secondNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int secondNewHash = secondNewEntry.hash(); + + assertTrue(headGroup.insertInChain(secondNewHash, secondNewEntry)); + assertChainContainsTag(secondNewTag, headGroup); + + String thirdNewTag = "new-tag-2"; + TagMap.Entry thirdNewEntry = TagMap.Entry.newObjectEntry(secondNewTag, "new"); + int thirdNewHash = secondNewEntry.hash(); + + assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); + assertChainDoesntContainTag(thirdNewTag, headGroup); + + assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); } - + @Test public void cloneChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); - TagMap.BucketGroup headGroup = fullGroup(30, secondGroup); - - TagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); - TagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; - TagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; - - assertGroupContentsStrictEquals(headGroup, clonedHeadGroup); - assertGroupContentsStrictEquals(secondGroup, clonedSecondGroup); - assertGroupContentsStrictEquals(firstGroup, clonedFirstGroup); + TagMap.BucketGroup firstGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); + TagMap.BucketGroup headGroup = fullGroup(30, secondGroup); + + TagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); + TagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; + TagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; + + assertGroupContentsStrictEquals(headGroup, clonedHeadGroup); + assertGroupContentsStrictEquals(secondGroup, clonedSecondGroup); + assertGroupContentsStrictEquals(firstGroup, clonedFirstGroup); } - + @Test public void removeGroupInChain() { - TagMap.BucketGroup tailGroup = fullGroup(10); - TagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); - TagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); - TagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); - TagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); - assertChain(headGroup, fourthGroup, thirdGroup, secondGroup, tailGroup); - - // need to test group removal - at head, middle, and tail of the chain - - // middle - assertSame(headGroup, headGroup.removeGroupInChain(thirdGroup)); - assertChain(headGroup, fourthGroup, secondGroup, tailGroup); - - // tail - assertSame(headGroup, headGroup.removeGroupInChain(tailGroup)); - assertChain(headGroup, fourthGroup, secondGroup); - - // head - assertSame(fourthGroup, headGroup.removeGroupInChain(headGroup)); - assertChain(fourthGroup, secondGroup); + TagMap.BucketGroup tailGroup = fullGroup(10); + TagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); + TagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); + TagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); + TagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); + assertChain(headGroup, fourthGroup, thirdGroup, secondGroup, tailGroup); + + // need to test group removal - at head, middle, and tail of the chain + + // middle + assertSame(headGroup, headGroup.removeGroupInChain(thirdGroup)); + assertChain(headGroup, fourthGroup, secondGroup, tailGroup); + + // tail + assertSame(headGroup, headGroup.removeGroupInChain(tailGroup)); + assertChain(headGroup, fourthGroup, secondGroup); + + // head + assertSame(fourthGroup, headGroup.removeGroupInChain(headGroup)); + assertChain(fourthGroup, secondGroup); } - + static final TagMap.BucketGroup fullGroup(int startingIndex) { - TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); - TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); - - TagMap.BucketGroup group = new TagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); - for ( int offset = 2; offset < TagMap.BucketGroup.LEN; ++offset ) { - TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); - group._insert(anotherEntry.hash(), anotherEntry); - } - return group; + TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); + TagMap.Entry secondEntry = + TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); + + TagMap.BucketGroup group = + new TagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + for (int offset = 2; offset < TagMap.BucketGroup.LEN; ++offset) { + TagMap.Entry anotherEntry = + TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); + group._insert(anotherEntry.hash(), anotherEntry); + } + return group; } - + static final TagMap.BucketGroup fullGroup(int startingIndex, TagMap.BucketGroup prev) { - TagMap.BucketGroup group = fullGroup(startingIndex); + TagMap.BucketGroup group = fullGroup(startingIndex); group.prev = prev; return group; } - + static final String tag(int startingIndex, int offset) { - return tag(startingIndex + offset); + return tag(startingIndex + offset); } - + static final String tag(int i) { - return "tag-" + i; + return "tag-" + i; } - + static final String value(int startingIndex, int offset) { - return value(startingIndex + offset); + return value(startingIndex + offset); } - + static final String value(int i) { - return "value-i"; + return "value-i"; } - + static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { - int hash = entry.hash(); - String tag = entry.tag(); - - assertSame(entry, group._find(hash, tag)); - - assertSame(entry, group.findInChain(hash, tag)); - assertSame(group, group.findContainingGroupInChain(hash, tag)); + int hash = entry.hash(); + String tag = entry.tag(); + + assertSame(entry, group._find(hash, tag)); + + assertSame(entry, group.findInChain(hash, tag)); + assertSame(group, group.findContainingGroupInChain(hash, tag)); } - + static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { - for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { + for (int i = 0; i < TagMap.BucketGroup.LEN; ++i) { assertNotSame(entry, group._entryAt(i)); } } - + static void assertChainContainsTag(String tag, TagMap.BucketGroup group) { - int hash = TagMap._hash(tag); - assertNotNull(group.findInChain(hash, tag)); + int hash = TagMap._hash(tag); + assertNotNull(group.findInChain(hash, tag)); } - + static void assertChainDoesntContainTag(String tag, TagMap.BucketGroup group) { - int hash = TagMap._hash(tag); - assertNull(group.findInChain(hash, tag)); + int hash = TagMap._hash(tag); + assertNull(group.findInChain(hash, tag)); } - - static void assertGroupContentsStrictEquals(TagMap.BucketGroup expected, TagMap.BucketGroup actual) { - for ( int i = 0; i < TagMap.BucketGroup.LEN; ++i ) { - assertEquals(expected._hashAt(i), actual._hashAt(i)); - assertSame(expected._entryAt(i), actual._entryAt(i)); - } + + static void assertGroupContentsStrictEquals( + TagMap.BucketGroup expected, TagMap.BucketGroup actual) { + for (int i = 0; i < TagMap.BucketGroup.LEN; ++i) { + assertEquals(expected._hashAt(i), actual._hashAt(i)); + assertSame(expected._entryAt(i), actual._entryAt(i)); + } } - + static void assertChain(TagMap.BucketGroup... chain) { - TagMap.BucketGroup cur; - int index; - for ( cur = chain[0], index = 0; cur != null; cur = cur.prev, ++index ) { - assertSame(chain[index], cur); - } - assertEquals(chain.length, index); + TagMap.BucketGroup cur; + int index; + for (cur = chain[0], index = 0; cur != null; cur = cur.prev, ++index) { + assertSame(chain[index], cur); + } + assertEquals(chain.length, index); } } From e7c6c26570fdc825c63ef19d7b7479ec7a3bc544 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 16:49:08 -0400 Subject: [PATCH 43/84] Tried default case per analysis tool - it was worse --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 0911189fdb1..563c0dee549 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1666,6 +1666,8 @@ Entry _entryAt(int index) { case 3: return this.entry3; + + // Do not use default case, that creates a 5% cost on entry handling } return null; @@ -1684,8 +1686,10 @@ int _hashAt(int index) { case 3: return this.hash3; + + // Do not use default case, that creates a 5% cost on entry handling } - + return 0; } From e474365251a5bd81d4b26648a94e2e7cff478c3e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 20 Mar 2025 17:19:30 -0400 Subject: [PATCH 44/84] More comments to clarify optimizations --- .../main/java/datadog/trace/core/DDSpanContext.java | 3 +++ .../src/main/java/datadog/trace/api/TagMap.java | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) 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 4efee4c0e6a..edd2bb56ed7 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 @@ -756,6 +756,9 @@ void setAllTags(final TagMap map, boolean needsIntercept) { synchronized (unsafeTags) { if (needsIntercept) { + // forEach out-performs the iterator of TagMap + // Taking advantage of ability to pass through other context arguments + // to avoid using a capturing lambda map.forEach( this, traceCollector.getTracer().getTagInterceptor(), diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 563c0dee549..5b44cc63d7e 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1666,8 +1666,8 @@ Entry _entryAt(int index) { case 3: return this.entry3; - - // Do not use default case, that creates a 5% cost on entry handling + + // Do not use default case, that creates a 5% cost on entry handling } return null; @@ -1686,10 +1686,10 @@ int _hashAt(int index) { case 3: return this.hash3; - - // Do not use default case, that creates a 5% cost on entry handling + + // Do not use default case, that creates a 5% cost on entry handling } - + return 0; } From c4f4a005c14e93aabe102e981b7951eb8dc6c9ae Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 25 Mar 2025 15:47:34 -0400 Subject: [PATCH 45/84] Fixing potential bug with SpanBuilder tag removal clobbering entry from another tag source Added a TagMap.Builder#smartRemove method smartRemove only adds to the Builder ledger if there's already corresponding put earlier in the ledger While this method is O(n), removes are assumed to be rare - and this is preferable to complicated handling in CoreSpanBuilder#setAllTags --- .../java/datadog/trace/core/CoreTracer.java | 6 +- .../main/java/datadog/trace/api/TagMap.java | 97 ++++++++++++++++--- .../datadog/trace/api/TagMapBuilderTest.java | 73 ++++++++------ 3 files changed, 136 insertions(+), 40 deletions(-) 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 9cef8d7eddc..1f7cf719170 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 @@ -1508,7 +1508,11 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { - tagBuilder.remove(tag); + // DQH - Use of smartRemove is important to avoid clobbering entries added by another map + // smartRemove only records the removal if a prior matching put has already occurred in the builder + // smartRemove is O(n) but since removes are rare, this is preferable to a more complicated + // implementation in setAll + tagBuilder.smartRemove(tag); } else { tagBuilder.put(tag, value); } diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 5b44cc63d7e..d9a827cf9cb 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1,5 +1,6 @@ package datadog.trace.api; +import datadog.trace.api.TagMap.Entry; import datadog.trace.api.function.TriConsumer; import java.util.AbstractCollection; import java.util.AbstractSet; @@ -1371,18 +1372,32 @@ private static final double prim2Double(long prim) { } } + /** + * TagMap.Builder maintains a ledger of Entry objects that can be used to construct a TagMap. + *

Since TagMap.Entry objects are immutable and shared, the cost of the TagMap.Builder is negligible + * as compared to most Builders. + *

TagMap.Builder's ledger can also be read directly to ensure that Entry-s are processed in order. + * In this way, TagMap.Builder can serve as a stand-in for a LinkedHashMap when Entry-s are not inserted + * more than once. + */ public static final class Builder implements Iterable { private Entry[] entries; private int nextPos = 0; + private boolean containsRemovals = false; - private Builder() { + Builder() { this(8); } - private Builder(int size) { + Builder(int size) { this.entries = new Entry[size]; } + /** + * Indicates if the ledger is completely empty + * + * build map still return an empty Map even if this method returns true. + */ public final boolean isDefinitelyEmpty() { return (this.nextPos == 0); } @@ -1396,48 +1411,106 @@ public final boolean isDefinitelyEmpty() { public final int estimateSize() { return this.nextPos; } + + /** + * Indicates if the ledger contains any removal entries + * @return + */ + public final boolean containsRemovals() { + return this.containsRemovals; + } public final Builder put(String tag, Object value) { - return this.put(Entry.newAnyEntry(tag, value)); + return this.recordEntry(Entry.newAnyEntry(tag, value)); } public final Builder put(String tag, CharSequence value) { - return this.put(Entry.newObjectEntry(tag, value)); + return this.recordEntry(Entry.newObjectEntry(tag, value)); } public final Builder put(String tag, boolean value) { - return this.put(Entry.newBooleanEntry(tag, value)); + return this.recordEntry(Entry.newBooleanEntry(tag, value)); } public final Builder put(String tag, int value) { - return this.put(Entry.newIntEntry(tag, value)); + return this.recordEntry(Entry.newIntEntry(tag, value)); } public final Builder put(String tag, long value) { - return this.put(Entry.newLongEntry(tag, value)); + return this.recordEntry(Entry.newLongEntry(tag, value)); } public final Builder put(String tag, float value) { - return this.put(Entry.newFloatEntry(tag, value)); + return this.recordEntry(Entry.newFloatEntry(tag, value)); } public final Builder put(String tag, double value) { - return this.put(Entry.newDoubleEntry(tag, value)); + return this.recordEntry(Entry.newDoubleEntry(tag, value)); } - - public final Builder remove(String tag) { - return this.put(Entry.newRemovalEntry(tag)); + + public final Builder uncheckedPut(Entry entry) { + return this.recordEntry(entry); } public final Builder put(Entry entry) { + this.recordChange(entry); + this.containsRemovals |= entry.isRemoval(); + return this; + } + + /** + * Records a removal Entry in the ledger + */ + public final Builder remove(String tag) { + return this.recordRemoval(Entry.newRemovalEntry(tag)); + } + + private final Builder recordEntry(Entry entry) { + this.recordChange(entry); + return this; + } + + private final Builder recordRemoval(Entry entry) { + this.recordChange(entry); + this.containsRemovals = true; + + return this; + } + + private final void recordChange(Entry entry) { if (this.nextPos >= this.entries.length) { this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); } this.entries[this.nextPos++] = entry; + } + + /** + * Smarter (but more expensive) version of {@link Builder#remove(String)} + * A removal entry is only added to the ledger if there is a prior insertion entry in the ledger + * + * NOTE: This method does require an O(n) traversal of the ledger + */ + public final Builder smartRemove(String tag) { + if ( this.contains(tag) ) { + this.remove(tag); + } return this; } + + private final boolean contains(String tag) { + Entry[] entries = this.entries; + + // min is to clamp, so ArrayBoundsCheckElimination optimization works + for ( int i = 0; i < Math.min(this.nextPos, entries.length); ++i ) { + if ( entries[i].matches(tag) ) return true; + } + return false; + } + /** + * Resets the ledger + */ public final void reset() { Arrays.fill(this.entries, null); this.nextPos = 0; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index 16ad7b68226..908eb3a31af 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -2,90 +2,109 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; public class TagMapBuilderTest { static final int SIZE = 32; - + @Test public void buildMutable() { TagMap.Builder builder = TagMap.builder(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.build(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + // just proving that the map is mutable map.set(key(1000), value(1000)); } - + @Test public void buildImmutable() { TagMap.Builder builder = TagMap.builder(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.buildImmutable(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + assertFrozen(map); } - + @Test public void buildWithRemoves() { TagMap.Builder builder = TagMap.builder(); - for (int i = 0; i < SIZE; ++i) { + for ( int i = 0; i < SIZE; ++i ) { builder.put(key(i), value(i)); } - - for (int i = 0; i < SIZE; i += 2) { + + for ( int i = 0; i < SIZE; i += 2 ) { builder.remove(key(i)); } TagMap map = builder.build(); - for (int i = 0; i < SIZE; ++i) { - if ((i % 2) == 0) { + for ( int i = 0; i < SIZE; ++i ) { + if ( (i % 2) == 0 ) { assertNull(map.getString(key(i))); } else { assertEquals(value(i), map.getString(key(i))); } } } - + + @Test + public void smartRemoval_existingCase() { + TagMap.Builder builder = TagMap.builder(); + builder.put("foo", "bar"); + builder.smartRemove("foo"); + + assertTrue(builder.containsRemovals()); + } + + @Test + public void smartRemoval_missingCase() { + TagMap.Builder builder = TagMap.builder(); + builder.smartRemove("foo"); + + assertFalse(builder.containsRemovals()); + } + @Test public void reset() { TagMap.Builder builder = TagMap.builder(2); - + builder.put(key(0), value(0)); TagMap map0 = builder.build(); - + builder.reset(); - + builder.put(key(1), value(1)); TagMap map1 = builder.build(); - + assertEquals(value(0), map0.getString(key(0))); assertNull(map1.getString(key(0))); - + assertNull(map0.getString(key(1))); assertEquals(value(1), map1.getString(key(1))); } - + static final String key(int i) { return "key-" + i; } @@ -93,12 +112,12 @@ static final String key(int i) { static final String value(int i) { return "value-" + i; } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch (IllegalStateException e) { + } catch ( IllegalStateException e ) { ex = e; } assertNotNull(ex); From 77eba4585ff0a94acff1f1eabc8a5bad717fcf28 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 25 Mar 2025 15:48:13 -0400 Subject: [PATCH 46/84] spotless --- .../java/datadog/trace/core/CoreTracer.java | 11 +-- .../main/java/datadog/trace/api/TagMap.java | 59 +++++++------- .../datadog/trace/api/TagMapBuilderTest.java | 78 +++++++++---------- 3 files changed, 74 insertions(+), 74 deletions(-) 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 1f7cf719170..873f67ff0c7 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 @@ -1508,11 +1508,12 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { this.tagBuilder = tagBuilder = TagMap.builder(); } if (value == null) { - // DQH - Use of smartRemove is important to avoid clobbering entries added by another map - // smartRemove only records the removal if a prior matching put has already occurred in the builder - // smartRemove is O(n) but since removes are rare, this is preferable to a more complicated - // implementation in setAll - tagBuilder.smartRemove(tag); + // DQH - Use of smartRemove is important to avoid clobbering entries added by another map + // smartRemove only records the removal if a prior matching put has already occurred in the + // builder + // smartRemove is O(n) but since removes are rare, this is preferable to a more complicated + // implementation in setAll + tagBuilder.smartRemove(tag); } else { tagBuilder.put(tag, value); } diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index d9a827cf9cb..6b9f42b7905 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1374,11 +1374,13 @@ private static final double prim2Double(long prim) { /** * TagMap.Builder maintains a ledger of Entry objects that can be used to construct a TagMap. - *

Since TagMap.Entry objects are immutable and shared, the cost of the TagMap.Builder is negligible - * as compared to most Builders. - *

TagMap.Builder's ledger can also be read directly to ensure that Entry-s are processed in order. - * In this way, TagMap.Builder can serve as a stand-in for a LinkedHashMap when Entry-s are not inserted - * more than once. + * + *

Since TagMap.Entry objects are immutable and shared, the cost of the TagMap.Builder is + * negligible as compared to most Builders. + * + *

TagMap.Builder's ledger can also be read directly to ensure that Entry-s are processed in + * order. In this way, TagMap.Builder can serve as a stand-in for a LinkedHashMap when Entry-s are + * not inserted more than once. */ public static final class Builder implements Iterable { private Entry[] entries; @@ -1395,8 +1397,8 @@ public static final class Builder implements Iterable { /** * Indicates if the ledger is completely empty - * - * build map still return an empty Map even if this method returns true. + * + *

build map still return an empty Map even if this method returns true. */ public final boolean isDefinitelyEmpty() { return (this.nextPos == 0); @@ -1411,9 +1413,10 @@ public final boolean isDefinitelyEmpty() { public final int estimateSize() { return this.nextPos; } - + /** * Indicates if the ledger contains any removal entries + * * @return */ public final boolean containsRemovals() { @@ -1447,7 +1450,7 @@ public final Builder put(String tag, float value) { public final Builder put(String tag, double value) { return this.recordEntry(Entry.newDoubleEntry(tag, value)); } - + public final Builder uncheckedPut(Entry entry) { return this.recordEntry(entry); } @@ -1458,25 +1461,23 @@ public final Builder put(Entry entry) { return this; } - /** - * Records a removal Entry in the ledger - */ + /** Records a removal Entry in the ledger */ public final Builder remove(String tag) { return this.recordRemoval(Entry.newRemovalEntry(tag)); } - + private final Builder recordEntry(Entry entry) { this.recordChange(entry); return this; } - + private final Builder recordRemoval(Entry entry) { this.recordChange(entry); this.containsRemovals = true; - + return this; } - + private final void recordChange(Entry entry) { if (this.nextPos >= this.entries.length) { this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); @@ -1484,33 +1485,31 @@ private final void recordChange(Entry entry) { this.entries[this.nextPos++] = entry; } - + /** - * Smarter (but more expensive) version of {@link Builder#remove(String)} - * A removal entry is only added to the ledger if there is a prior insertion entry in the ledger - * - * NOTE: This method does require an O(n) traversal of the ledger + * Smarter (but more expensive) version of {@link Builder#remove(String)} A removal entry is + * only added to the ledger if there is a prior insertion entry in the ledger + * + *

NOTE: This method does require an O(n) traversal of the ledger */ public final Builder smartRemove(String tag) { - if ( this.contains(tag) ) { - this.remove(tag); + if (this.contains(tag)) { + this.remove(tag); } return this; } - + private final boolean contains(String tag) { Entry[] entries = this.entries; - + // min is to clamp, so ArrayBoundsCheckElimination optimization works - for ( int i = 0; i < Math.min(this.nextPos, entries.length); ++i ) { - if ( entries[i].matches(tag) ) return true; + for (int i = 0; i < Math.min(this.nextPos, entries.length); ++i) { + if (entries[i].matches(tag)) return true; } return false; } - /** - * Resets the ledger - */ + /** Resets the ledger */ public final void reset() { Arrays.fill(this.entries, null); this.nextPos = 0; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index 908eb3a31af..a9be9881533 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -1,110 +1,110 @@ package datadog.trace.api; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; public class TagMapBuilderTest { static final int SIZE = 32; - + @Test public void buildMutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + // just proving that the map is mutable map.set(key(1000), value(1000)); } - + @Test public void buildImmutable() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - + assertEquals(SIZE, builder.estimateSize()); - + TagMap map = builder.buildImmutable(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } assertEquals(SIZE, map.computeSize()); - + assertFrozen(map); } - + @Test public void buildWithRemoves() { TagMap.Builder builder = TagMap.builder(); - for ( int i = 0; i < SIZE; ++i ) { + for (int i = 0; i < SIZE; ++i) { builder.put(key(i), value(i)); } - - for ( int i = 0; i < SIZE; i += 2 ) { + + for (int i = 0; i < SIZE; i += 2) { builder.remove(key(i)); } TagMap map = builder.build(); - for ( int i = 0; i < SIZE; ++i ) { - if ( (i % 2) == 0 ) { + for (int i = 0; i < SIZE; ++i) { + if ((i % 2) == 0) { assertNull(map.getString(key(i))); } else { assertEquals(value(i), map.getString(key(i))); } } } - + @Test public void smartRemoval_existingCase() { - TagMap.Builder builder = TagMap.builder(); - builder.put("foo", "bar"); - builder.smartRemove("foo"); - - assertTrue(builder.containsRemovals()); + TagMap.Builder builder = TagMap.builder(); + builder.put("foo", "bar"); + builder.smartRemove("foo"); + + assertTrue(builder.containsRemovals()); } - + @Test public void smartRemoval_missingCase() { - TagMap.Builder builder = TagMap.builder(); - builder.smartRemove("foo"); - - assertFalse(builder.containsRemovals()); + TagMap.Builder builder = TagMap.builder(); + builder.smartRemove("foo"); + + assertFalse(builder.containsRemovals()); } - + @Test public void reset() { TagMap.Builder builder = TagMap.builder(2); - + builder.put(key(0), value(0)); TagMap map0 = builder.build(); - + builder.reset(); - + builder.put(key(1), value(1)); TagMap map1 = builder.build(); - + assertEquals(value(0), map0.getString(key(0))); assertNull(map1.getString(key(0))); - + assertNull(map0.getString(key(1))); assertEquals(value(1), map1.getString(key(1))); } - + static final String key(int i) { return "key-" + i; } @@ -112,12 +112,12 @@ static final String key(int i) { static final String value(int i) { return "value-" + i; } - + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { map.put("foo", "bar"); - } catch ( IllegalStateException e ) { + } catch (IllegalStateException e) { ex = e; } assertNotNull(ex); From d3cc0629c66f44fe1b022da151e9a2b047484703 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 11:26:09 -0400 Subject: [PATCH 47/84] Progress on fixing failing tests Almost all the tests are now passing The primary problem was that SpanInfo.getTags is now expected to return a TagMap. The tests using Mock-s were using regular Maps instead Updated those tests to wrap the Map literal in TagMap.fromMap --- .../datadog/iast/sink/HstsMissingHeaderModuleTest.groovy | 5 +++-- .../iast/sink/InsecureAuthProtocolModuleTest.groovy | 5 +++-- .../iast/sink/XContentTypeOptionsModuleTest.groovy | 9 +++++---- .../com/datadog/appsec/AppSecSystemSpecification.groovy | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy index bc5f5ef305a..84c2858f654 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/HstsMissingHeaderModuleTest.groovy @@ -7,6 +7,7 @@ import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType import com.datadog.iast.overhead.Operation import com.datadog.iast.overhead.OverheadController +import datadog.trace.api.TagMap import datadog.trace.api.gateway.Flow import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.internal.TraceSegment @@ -45,10 +46,10 @@ class HstsMissingHeaderModuleTest extends IastModuleImplTestBase { final handler = new RequestEndedHandler(dependencies) ctx.xForwardedProto = 'https' ctx.contentType = "text/html" - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.url': 'https://localhost/a', 'http.status_code': 200i - ] + ]) when: def flow = handler.apply(reqCtx, span) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy index 882f533e6f7..646778e34b7 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/InsecureAuthProtocolModuleTest.groovy @@ -5,6 +5,7 @@ import com.datadog.iast.Reporter import com.datadog.iast.RequestEndedHandler import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import datadog.trace.api.TagMap import datadog.trace.api.gateway.Flow import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.iast.sink.InsecureAuthProtocolModule @@ -42,9 +43,9 @@ class InsecureAuthProtocolModuleTest extends IastModuleImplTestBase{ given: final handler = new RequestEndedHandler(dependencies) ctx.authorization = value - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.status_code': status_code - ] + ]) when: def flow = handler.apply(reqCtx, span) diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy index 7224ca49a24..c7f0eae4198 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/sink/XContentTypeOptionsModuleTest.groovy @@ -5,6 +5,7 @@ import com.datadog.iast.Reporter import com.datadog.iast.RequestEndedHandler import com.datadog.iast.model.Vulnerability import com.datadog.iast.model.VulnerabilityType +import datadog.trace.api.TagMap import datadog.trace.api.gateway.Flow import datadog.trace.api.iast.InstrumentationBridge import datadog.trace.api.internal.TraceSegment @@ -33,9 +34,9 @@ class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { given: final handler = new RequestEndedHandler(dependencies) ctx.contentType = "text/html" - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.status_code': 200i - ] + ]) when: def flow = handler.apply(reqCtx, span) @@ -56,10 +57,10 @@ class XContentTypeOptionsModuleTest extends IastModuleImplTestBase { final handler = new RequestEndedHandler(dependencies) ctx.xForwardedProto = 'https' ctx.contentType = "text/html" - span.getTags() >> [ + span.getTags() >> TagMap.fromMap([ 'http.url': url, 'http.status_code': status - ] + ]) when: def flow = handler.apply(reqCtx, span) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy index a92960e0a13..52292a28a33 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/AppSecSystemSpecification.groovy @@ -13,6 +13,7 @@ import datadog.remoteconfig.ConfigurationEndListener import datadog.remoteconfig.ConfigurationPoller import datadog.remoteconfig.Product import datadog.trace.api.Config +import datadog.trace.api.TagMap import datadog.trace.api.internal.TraceSegment import datadog.trace.api.gateway.Flow import datadog.trace.api.gateway.IGSpanInfo @@ -72,7 +73,7 @@ class AppSecSystemSpecification extends DDSpecification { requestEndedCB.apply(requestContext, span) then: - 1 * span.getTags() >> ['http.client_ip':'1.1.1.1'] + 1 * span.getTags() >> TagMap.fromMap(['http.client_ip':'1.1.1.1']) 1 * subService.registerCallback(EVENTS.requestEnded(), _) >> { requestEndedCB = it[1]; null } 1 * requestContext.getData(RequestContextSlot.APPSEC) >> appSecReqCtx 1 * requestContext.traceSegment >> traceSegment From 2c8c3ff18a1d15bc38aaf810be5e6c0558558ec3 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 11:53:08 -0400 Subject: [PATCH 48/84] Fixed the last getTags() mock that I missed previously --- .../appsec/gateway/GatewayBridgeSpecification.groovy | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy index c13156d461b..35974d27910 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy @@ -1,3 +1,4 @@ + package com.datadog.appsec.gateway import com.datadog.appsec.AppSecSystem @@ -8,6 +9,7 @@ import com.datadog.appsec.event.data.DataBundle import com.datadog.appsec.event.data.KnownAddresses import com.datadog.appsec.report.AppSecEvent import com.datadog.appsec.report.AppSecEventWrapper +import datadog.trace.api.TagMap import datadog.trace.api.function.TriConsumer import datadog.trace.api.function.TriFunction import datadog.trace.api.gateway.BlockResponseFunction @@ -156,7 +158,7 @@ class GatewayBridgeSpecification extends DDSpecification { def flow = requestEndedCB.apply(mockCtx, spanInfo) then: - 1 * spanInfo.getTags() >> ['http.client_ip': '1.1.1.1'] + 1 * spanInfo.getTags() >> TagMap.fromMap(['http.client_ip': '1.1.1.1']) 1 * mockAppSecCtx.transferCollectedEvents() >> [event] 1 * mockAppSecCtx.peerAddress >> '2001::1' 1 * mockAppSecCtx.close(false) @@ -189,7 +191,7 @@ class GatewayBridgeSpecification extends DDSpecification { then: 1 * mockAppSecCtx.transferCollectedEvents() >> [Stub(AppSecEvent)] - 1 * spanInfo.getTags() >> ['http.client_ip': '8.8.8.8'] + 1 * spanInfo.getTags() >> TagMap.fromMap(['http.client_ip': '8.8.8.8']) 1 * traceSegment.setTagTop('actor.ip', '8.8.8.8') } From cae4880cac8a5f04db4c27ca2b98ecb0bfb3f62a Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 12:30:53 -0400 Subject: [PATCH 49/84] spotless --- .../datadog/trace/core/tagprocessor/SpanPointersProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eb88235fe65..4dce08ba8e7 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 @@ -161,4 +161,4 @@ private static AgentSpanLink buildSpanPointer(String hash, String ptrKind) { return SpanLink.from(noopSpanContext(), DEFAULT_FLAGS, "", attributes); } -} \ No newline at end of file +} From b0e0a88a594d407397ac2edc3f08da0ccf911132 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 18:01:57 -0400 Subject: [PATCH 50/84] Removing unnecessary comment --- .../datadog/trace/bootstrap/instrumentation/api/TagContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index d91b41ce72e..d833e5760bf 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -168,7 +168,6 @@ public String getCustomIpHeader() { } public final TagMap getTags() { - // DQH - Because of the lazy in putTag, this method effectively returns an immutable map return (this.tags == null) ? TagMap.EMPTY : this.tags; } From db83248b7c4dd5f68ac50658bfb942e5d08ed1d9 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 19:00:00 -0400 Subject: [PATCH 51/84] Fixing infinite recursion found by SpotBugs --- .../src/main/java/datadog/trace/core/DDSpanContext.java | 4 ++++ 1 file changed, 4 insertions(+) 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 edd2bb56ed7..7f482d11f31 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 @@ -748,6 +748,10 @@ public void setTag(final String tag, final Object value) { } } } + + void setAllTags(final TagMap map) { + setAllTags(map, true); + } void setAllTags(final TagMap map, boolean needsIntercept) { if (map == null) { From 6168fec5783dd83cb4324e84c217cf8de5c941cf Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 19:08:16 -0400 Subject: [PATCH 52/84] spotless --- .../src/main/java/datadog/trace/core/DDSpanContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7f482d11f31..84c2c439874 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 @@ -748,9 +748,9 @@ public void setTag(final String tag, final Object value) { } } } - + void setAllTags(final TagMap map) { - setAllTags(map, true); + setAllTags(map, true); } void setAllTags(final TagMap map, boolean needsIntercept) { From 12f97b7acf51a676c989f21547b8bf452c7bcd5f Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 19:51:33 -0400 Subject: [PATCH 53/84] quieting spotbugs --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 6b9f42b7905..b899eec036e 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1068,6 +1068,10 @@ public final Object objectValue() { case DOUBLE: this.obj = prim2Double(this.prim); break; + + default: + // DQH - quiet spotbugs + break; } if (this.is(REMOVED)) { From 6f411517408a0f8bdef5555b904c2f278cdd0eb7 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 20:10:47 -0400 Subject: [PATCH 54/84] spotless --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index b899eec036e..cfc000bd6a2 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1068,10 +1068,10 @@ public final Object objectValue() { case DOUBLE: this.obj = prim2Double(this.prim); break; - + default: // DQH - quiet spotbugs - break; + break; } if (this.is(REMOVED)) { From b1ddc8de54694106cf5a7ad390071d2d39a6d11d Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 20:33:52 -0400 Subject: [PATCH 55/84] Fix-up after rebase oversight --- dd-trace-core/src/main/java/datadog/trace/core/CoreTracer.java | 3 --- 1 file changed, 3 deletions(-) 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 873f67ff0c7..b9553849815 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 @@ -800,9 +800,6 @@ private CoreTracer( Propagators.register(BAGGAGE_CONCERN, new BaggagePropagator(config)); } - this.tagInterceptor = - null == tagInterceptor ? new TagInterceptor(new RuleFlags(config)) : tagInterceptor; - if (config.isCiVisibilityEnabled()) { if (config.isCiVisibilityTraceSanitationEnabled()) { addTraceInterceptor(CiVisibilityTraceInterceptor.INSTANCE); From 2413e1de33646073c87681069ba3052d58aba8a3 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 26 Mar 2025 20:36:50 -0400 Subject: [PATCH 56/84] spotless --- .../datadog/trace/core/tagprocessor/SpanPointersProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 50035a6028e..8282583cbf2 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 @@ -39,7 +39,7 @@ public class SpanPointersProcessor extends TagsPostProcessor { @Override public void processTags( TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { - // DQH - TODO - There's a lot room to optimize this using TagMap's capabilities + // DQH - TODO - There's a lot room to optimize this using TagMap's capabilities AgentSpanLink s3Link = handleS3SpanPointer(unsafeTags); if (s3Link != null) { spanLinks.add(s3Link); From 5ff6f8f80b32a008abb0b86ede3d954168a16a62 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 27 Mar 2025 11:24:30 -0400 Subject: [PATCH 57/84] Improving TagMap test coverage - Adding more tests for TagMap.Builder & TagMap.Entry --- .../main/java/datadog/trace/api/TagMap.java | 177 ++++++++---------- .../datadog/trace/api/TagMapBuilderTest.java | 69 +++++++ .../datadog/trace/api/TagMapEntryTest.java | 31 ++- 3 files changed, 178 insertions(+), 99 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index cfc000bd6a2..818a8686bc0 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1,6 +1,5 @@ package datadog.trace.api; -import datadog.trace.api.TagMap.Entry; import datadog.trace.api.function.TriConsumer; import java.util.AbstractCollection; import java.util.AbstractSet; @@ -902,7 +901,7 @@ static final Entry newRemovalEntry(String tag) { * hash is stored in line for fast handling of Entry-s coming another Tag * However, hash is lazily computed using the same trick as {@link java.lang.String}. */ - int hash; + int lazyHash; // To optimize construction of Entry around boxed primitives and Object entries, // no type checks are done during construction. @@ -918,18 +917,19 @@ static final Entry newRemovalEntry(String tag) { // Type and prim cannot use the same trick as hash because during ANY resolution the order of // writes is important - volatile byte type; - volatile long prim; - volatile Object obj; + volatile byte rawType; + volatile long rawPrim; + volatile Object rawObj; volatile String strCache = null; private Entry(String tag, byte type, long prim, Object obj) { this.tag = tag; - this.hash = 0; // lazily computed - this.type = type; - this.prim = prim; - this.obj = obj; + this.lazyHash = 0; // lazily computed + + this.rawType = type; + this.rawPrim = prim; + this.rawObj = obj; } public final String tag() { @@ -939,11 +939,11 @@ public final String tag() { int hash() { // If value of hash read in this thread is zero, then hash is computed. // hash is not held as a volatile, since this computation can safely be repeated as any time - int hash = this.hash; + int hash = this.lazyHash; if (hash != 0) return hash; hash = _hash(this.tag); - this.hash = hash; + this.lazyHash = hash; return hash; } @@ -952,7 +952,7 @@ public final byte type() { } public final boolean is(byte type) { - byte curType = this.type; + byte curType = this.rawType; if (curType == type) { return true; } else if (curType != ANY) { @@ -963,7 +963,7 @@ public final boolean is(byte type) { } public final boolean isNumericPrimitive() { - byte curType = this.type; + byte curType = this.rawType; if (_isNumericPrimitive(curType)) { return true; } else if (curType != ANY) { @@ -974,8 +974,8 @@ public final boolean isNumericPrimitive() { } public final boolean isNumber() { - byte curType = this.type; - return _isNumericPrimitive(curType) || (this.obj instanceof Number); + byte curType = this.rawType; + return _isNumericPrimitive(curType) || (this.rawObj instanceof Number); } private static final boolean _isNumericPrimitive(byte type) { @@ -983,10 +983,10 @@ private static final boolean _isNumericPrimitive(byte type) { } private final byte resolveAny() { - byte curType = this.type; + byte curType = this.rawType; if (curType != ANY) return curType; - Object value = this.obj; + Object value = this.rawObj; long prim; byte resolvedType; @@ -1024,8 +1024,8 @@ private void _setPrim(byte type, long prim) { // Order is important here, the contract is that prim must be set properly *before* // type is set to a non-object type - this.prim = prim; - this.type = type; + this.rawPrim = prim; + this.rawType = type; } public final boolean isObject() { @@ -1041,36 +1041,32 @@ public final boolean matches(String tag) { } public final Object objectValue() { - if (this.obj != null) { - return this.obj; + if (this.rawObj != null) { + return this.rawObj; } // This code doesn't need to handle ANY-s. // An entry that starts as an ANY will always have this.obj set - switch (this.type) { + switch (this.rawType) { case BOOLEAN: - this.obj = prim2Boolean(this.prim); + this.rawObj = prim2Boolean(this.rawPrim); break; case INT: // Maybe use a wider cache that handles response code??? - this.obj = prim2Int(this.prim); + this.rawObj = prim2Int(this.rawPrim); break; case LONG: - this.obj = prim2Long(this.prim); + this.rawObj = prim2Long(this.rawPrim); break; case FLOAT: - this.obj = prim2Float(this.prim); + this.rawObj = prim2Float(this.rawPrim); break; case DOUBLE: - this.obj = prim2Double(this.prim); - break; - - default: - // DQH - quiet spotbugs + this.rawObj = prim2Double(this.rawPrim); break; } @@ -1078,23 +1074,23 @@ public final Object objectValue() { return null; } - return this.obj; + return this.rawObj; } public final boolean booleanValue() { - byte type = this.type; + byte type = this.rawType; if (type == BOOLEAN) { - return prim2Boolean(this.prim); - } else if (type == ANY && this.obj instanceof Boolean) { - boolean boolValue = (Boolean) this.obj; + return prim2Boolean(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Boolean) { + boolean boolValue = (Boolean) this.rawObj; this._setPrim(BOOLEAN, boolean2Prim(boolValue)); return boolValue; } // resolution will set prim if necessary byte resolvedType = this.resolveAny(); - long prim = this.prim; + long prim = this.rawPrim; switch (resolvedType) { case INT: @@ -1110,7 +1106,7 @@ public final boolean booleanValue() { return prim2Double(prim) != 0D; case OBJECT: - return (this.obj != null); + return (this.rawObj != null); case REMOVED: return false; @@ -1120,19 +1116,19 @@ public final boolean booleanValue() { } public final int intValue() { - byte type = this.type; + byte type = this.rawType; if (type == INT) { - return prim2Int(this.prim); - } else if (type == ANY && this.obj instanceof Integer) { - int intValue = (Integer) this.obj; + return prim2Int(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Integer) { + int intValue = (Integer) this.rawObj; this._setPrim(INT, int2Prim(intValue)); return intValue; } // resolution will set prim if necessary byte resolvedType = this.resolveAny(); - long prim = this.prim; + long prim = this.rawPrim; switch (resolvedType) { case BOOLEAN: @@ -1158,19 +1154,19 @@ public final int intValue() { } public final long longValue() { - byte type = this.type; + byte type = this.rawType; if (type == LONG) { - return prim2Long(this.prim); - } else if (type == ANY && this.obj instanceof Long) { - long longValue = (Long) this.obj; + return prim2Long(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Long) { + long longValue = (Long) this.rawObj; this._setPrim(LONG, long2Prim(longValue)); return longValue; } // resolution will set prim if necessary byte resolvedType = this.resolveAny(); - long prim = this.prim; + long prim = this.rawPrim; switch (resolvedType) { case BOOLEAN: @@ -1196,19 +1192,19 @@ public final long longValue() { } public final float floatValue() { - byte type = this.type; + byte type = this.rawType; if (type == FLOAT) { - return prim2Float(this.prim); - } else if (type == ANY && this.obj instanceof Float) { - float floatValue = (Float) this.obj; + return prim2Float(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Float) { + float floatValue = (Float) this.rawObj; this._setPrim(FLOAT, float2Prim(floatValue)); return floatValue; } // resolution will set prim if necessary byte resolvedType = this.resolveAny(); - long prim = this.prim; + long prim = this.rawPrim; switch (resolvedType) { case BOOLEAN: @@ -1234,19 +1230,19 @@ public final float floatValue() { } public final double doubleValue() { - byte type = this.type; + byte type = this.rawType; if (type == DOUBLE) { - return prim2Double(this.prim); - } else if (type == ANY && this.obj instanceof Double) { - double doubleValue = (Double) this.obj; + return prim2Double(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Double) { + double doubleValue = (Double) this.rawObj; this._setPrim(DOUBLE, double2Prim(doubleValue)); return doubleValue; } // resolution will set prim if necessary byte resolvedType = this.resolveAny(); - long prim = this.prim; + long prim = this.rawPrim; switch (resolvedType) { case BOOLEAN: @@ -1285,28 +1281,28 @@ public final String stringValue() { private final String computeStringValue() { // Could do type resolution here, // but decided to just fallback to this.obj.toString() for ANY case - switch (this.type) { + switch (this.rawType) { case BOOLEAN: - return Boolean.toString(prim2Boolean(this.prim)); + return Boolean.toString(prim2Boolean(this.rawPrim)); case INT: - return Integer.toString(prim2Int(this.prim)); + return Integer.toString(prim2Int(this.rawPrim)); case LONG: - return Long.toString(prim2Long(this.prim)); + return Long.toString(prim2Long(this.rawPrim)); case FLOAT: - return Float.toString(prim2Float(this.prim)); + return Float.toString(prim2Float(this.rawPrim)); case DOUBLE: - return Double.toString(prim2Double(this.prim)); + return Double.toString(prim2Double(this.rawPrim)); case REMOVED: return null; case OBJECT: case ANY: - return this.obj.toString(); + return this.rawObj.toString(); } return null; @@ -1376,34 +1372,19 @@ private static final double prim2Double(long prim) { } } - /** - * TagMap.Builder maintains a ledger of Entry objects that can be used to construct a TagMap. - * - *

Since TagMap.Entry objects are immutable and shared, the cost of the TagMap.Builder is - * negligible as compared to most Builders. - * - *

TagMap.Builder's ledger can also be read directly to ensure that Entry-s are processed in - * order. In this way, TagMap.Builder can serve as a stand-in for a LinkedHashMap when Entry-s are - * not inserted more than once. - */ public static final class Builder implements Iterable { private Entry[] entries; private int nextPos = 0; private boolean containsRemovals = false; - Builder() { + private Builder() { this(8); } - Builder(int size) { + private Builder(int size) { this.entries = new Entry[size]; } - /** - * Indicates if the ledger is completely empty - * - *

build map still return an empty Map even if this method returns true. - */ public final boolean isDefinitelyEmpty() { return (this.nextPos == 0); } @@ -1418,11 +1399,6 @@ public final int estimateSize() { return this.nextPos; } - /** - * Indicates if the ledger contains any removal entries - * - * @return - */ public final boolean containsRemovals() { return this.containsRemovals; } @@ -1465,7 +1441,6 @@ public final Builder put(Entry entry) { return this; } - /** Records a removal Entry in the ledger */ public final Builder remove(String tag) { return this.recordRemoval(Entry.newRemovalEntry(tag)); } @@ -1490,12 +1465,6 @@ private final void recordChange(Entry entry) { this.entries[this.nextPos++] = entry; } - /** - * Smarter (but more expensive) version of {@link Builder#remove(String)} A removal entry is - * only added to the ledger if there is a prior insertion entry in the ledger - * - *

NOTE: This method does require an O(n) traversal of the ledger - */ public final Builder smartRemove(String tag) { if (this.contains(tag)) { this.remove(tag); @@ -1504,16 +1473,28 @@ public final Builder smartRemove(String tag) { } private final boolean contains(String tag) { - Entry[] entries = this.entries; + Entry[] thisEntries = this.entries; // min is to clamp, so ArrayBoundsCheckElimination optimization works - for (int i = 0; i < Math.min(this.nextPos, entries.length); ++i) { - if (entries[i].matches(tag)) return true; + for (int i = 0; i < Math.min(this.nextPos, thisEntries.length); ++i) { + if (thisEntries[i].matches(tag)) return true; } return false; } - /** Resets the ledger */ + /* + * Just for testing + */ + final Entry findLastEntry(String tag) { + Entry[] thisEntries = this.entries; + + // min is to clamp, so ArrayBoundsCheckElimination optimization works + for (int i = Math.min(this.nextPos, thisEntries.length) - 1; i >= 0; --i) { + if (thisEntries[i].matches(tag)) return thisEntries[i]; + } + return null; + } + public final void reset() { Arrays.fill(this.entries, null); this.nextPos = 0; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index a9be9881533..143eb9bb43f 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -11,6 +11,44 @@ public class TagMapBuilderTest { static final int SIZE = 32; + @Test + public void inOrder() { + TagMap.Builder builder = TagMap.builder(); + for (int i = 0; i < SIZE; ++i) { + builder.put(key(i), value(i)); + } + + assertEquals(SIZE, builder.estimateSize()); + + int i = 0; + for (TagMap.Entry entry : builder) { + assertEquals(key(i), entry.tag()); + assertEquals(value(i), entry.stringValue()); + + ++i; + } + } + + @Test + public void testTypes() { + TagMap.Builder builder = TagMap.builder(); + builder.put("bool", true); + builder.put("int", 1); + builder.put("long", 1L); + builder.put("float", 1F); + builder.put("double", 1D); + builder.put("object", (Object) "string"); + builder.put("string", "string"); + + assertEntryRawType(TagMap.Entry.BOOLEAN, builder, "bool"); + assertEntryRawType(TagMap.Entry.INT, builder, "int"); + assertEntryRawType(TagMap.Entry.LONG, builder, "long"); + assertEntryRawType(TagMap.Entry.FLOAT, builder, "float"); + assertEntryRawType(TagMap.Entry.DOUBLE, builder, "double"); + assertEntryRawType(TagMap.Entry.ANY, builder, "object"); + assertEntryRawType(TagMap.Entry.OBJECT, builder, "string"); + } + @Test public void buildMutable() { TagMap.Builder builder = TagMap.builder(); @@ -48,6 +86,32 @@ public void buildImmutable() { assertFrozen(map); } + @Test + public void builderExpansion() { + TagMap.Builder builder = TagMap.builder(); + for (int i = 0; i < 100; ++i) { + builder.put(key(i), value(i)); + } + + TagMap map = builder.build(); + for (int i = 0; i < 100; ++i) { + assertEquals(value(i), map.getString(key(i))); + } + } + + @Test + public void builderPresized() { + TagMap.Builder builder = TagMap.builder(100); + for (int i = 0; i < 100; ++i) { + builder.put(key(i), value(i)); + } + + TagMap map = builder.build(); + for (int i = 0; i < 100; ++i) { + assertEquals(value(i), map.getString(key(i))); + } + } + @Test public void buildWithRemoves() { TagMap.Builder builder = TagMap.builder(); @@ -113,6 +177,11 @@ static final String value(int i) { return "value-" + i; } + static final void assertEntryRawType(byte expectedType, TagMap.Builder builder, String tag) { + TagMap.Entry entry = builder.findLastEntry(tag); + assertEquals(expectedType, entry.rawType); + } + static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 0d66911d168..02c7acc3eb2 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -10,6 +10,8 @@ public class TagMapEntryTest { @Test public void objectEntry() { TagMap.Entry entry = TagMap.Entry.newObjectEntry("foo", "bar"); + assertEquals(TagMap.Entry.OBJECT, entry.rawType); + assertKey("foo", entry); assertValue("bar", entry); @@ -21,6 +23,7 @@ public void objectEntry() { @Test public void anyEntry_object() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", "bar"); + assertEquals(TagMap.Entry.ANY, entry.rawType); assertKey("foo", entry); assertValue("bar", entry); @@ -34,6 +37,7 @@ public void anyEntry_object() { @Test public void booleanEntry() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", true); + assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); assertKey("foo", entry); assertValue(true, entry); @@ -45,6 +49,7 @@ public void booleanEntry() { @Test public void booleanEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); + assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); assertKey("foo", entry); assertValue(true, entry); @@ -55,7 +60,8 @@ public void booleanEntry_boxed() { @Test public void anyEntry_boolean() { - TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(true)); + assertEquals(TagMap.Entry.ANY, entry.rawType); assertKey("foo", entry); assertValue(true, entry); @@ -69,6 +75,7 @@ public void anyEntry_boolean() { @Test public void intEntry() { TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", 20); + assertEquals(TagMap.Entry.INT, entry.rawType); assertKey("foo", entry); assertValue(20, entry); @@ -80,6 +87,7 @@ public void intEntry() { @Test public void intEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)); + assertEquals(TagMap.Entry.INT, entry.rawType); assertKey("foo", entry); assertValue(20, entry); @@ -91,6 +99,7 @@ public void intEntry_boxed() { @Test public void anyEntry_int() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)); + assertEquals(TagMap.Entry.ANY, entry.rawType); assertKey("foo", entry); assertValue(20, entry); @@ -104,6 +113,7 @@ public void anyEntry_int() { @Test public void longEntry() { TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", 1_048_576L); + assertEquals(TagMap.Entry.LONG, entry.rawType); assertKey("foo", entry); assertValue(1_048_576L, entry); @@ -115,6 +125,7 @@ public void longEntry() { @Test public void longEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)); + assertEquals(TagMap.Entry.LONG, entry.rawType); assertKey("foo", entry); assertValue(1_048_576L, entry); @@ -126,6 +137,7 @@ public void longEntry_boxed() { @Test public void anyEntry_long() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)); + assertEquals(TagMap.Entry.ANY, entry.rawType); assertKey("foo", entry); assertValue(1_048_576L, entry); @@ -141,6 +153,7 @@ public void anyEntry_long() { @Test public void doubleEntry() { TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); + assertEquals(TagMap.Entry.DOUBLE, entry.rawType); assertKey("foo", entry); assertValue(Math.PI, entry); @@ -152,6 +165,7 @@ public void doubleEntry() { @Test public void doubleEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); + assertEquals(TagMap.Entry.DOUBLE, entry.rawType); assertKey("foo", entry); assertValue(Math.PI, entry); @@ -163,6 +177,7 @@ public void doubleEntry_boxed() { @Test public void anyEntry_double() { TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)); + assertEquals(TagMap.Entry.ANY, entry.rawType); assertKey("foo", entry); assertValue(Math.PI, entry); @@ -178,6 +193,7 @@ public void anyEntry_double() { @Test public void floatEntry() { TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); + assertEquals(TagMap.Entry.FLOAT, entry.rawType); assertKey("foo", entry); assertValue(2.718281828f, entry); @@ -189,6 +205,19 @@ public void floatEntry() { @Test public void floatEntry_boxed() { TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); + assertEquals(TagMap.Entry.FLOAT, entry.rawType); + + assertKey("foo", entry); + assertValue(2.718281828f, entry); + + assertTrue(entry.isNumericPrimitive()); + assertTrue(entry.is(TagMap.Entry.FLOAT)); + } + + @Test + public void anyEntry_float() { + TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Float.valueOf(2.718281828f)); + assertEquals(TagMap.Entry.ANY, entry.rawType); assertKey("foo", entry); assertValue(2.718281828f, entry); From d3b4a0ec0f502d5e7b35f470efb7a5de744eb4ee Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 27 Mar 2025 11:36:16 -0400 Subject: [PATCH 58/84] More TagMap.Builder coverage --- .../datadog/trace/api/TagMapBuilderTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index 143eb9bb43f..e8067c96b5b 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -2,7 +2,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -86,6 +88,31 @@ public void buildImmutable() { assertFrozen(map); } + @Test + public void build_empty() { + TagMap.Builder builder = TagMap.builder(); + assertTrue(builder.isDefinitelyEmpty()); + assertNotSame(TagMap.EMPTY, builder.build()); + } + + @Test + public void buildImmutable_empty() { + TagMap.Builder builder = TagMap.builder(); + assertTrue(builder.isDefinitelyEmpty()); + assertSame(TagMap.EMPTY, builder.buildImmutable()); + } + + @Test + public void isDefinitelyEmpty_emptyMap() { + TagMap.Builder builder = TagMap.builder(); + builder.put("foo", "bar"); + builder.remove("foo"); + + assertFalse(builder.isDefinitelyEmpty()); + TagMap map = builder.build(); + assertTrue(map.checkIfEmpty()); + } + @Test public void builderExpansion() { TagMap.Builder builder = TagMap.builder(); From 433650dc3677c68e79d251158b160e2f6ec1ba4b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 27 Mar 2025 14:11:57 -0400 Subject: [PATCH 59/84] More TagMap test coverage - entrySet, keySet, values --- .../java/datadog/trace/api/TagMapTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index d441ae382cc..11737e82508 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -7,8 +7,10 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import org.junit.jupiter.api.Test; @@ -267,6 +269,54 @@ public void forEachTriConsumer() { assertTrue(keys.isEmpty()); } + @Test + public void entrySet() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set> actualEntries = map.entrySet(); + assertEquals(size, actualEntries.size()); + assertFalse(actualEntries.isEmpty()); + + Set expectedKeys = expectedKeys(size); + for (Map.Entry entry : actualEntries) { + assertTrue(expectedKeys.remove(entry.getKey())); + } + assertTrue(expectedKeys.isEmpty()); + } + + @Test + public void keySet() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Set actualKeys = map.keySet(); + assertEquals(size, actualKeys.size()); + assertFalse(actualKeys.isEmpty()); + + Set expectedKeys = expectedKeys(size); + for (String key : actualKeys) { + assertTrue(expectedKeys.remove(key)); + } + assertTrue(expectedKeys.isEmpty()); + } + + @Test + public void values() { + int size = randomSize(); + TagMap map = createTagMap(size); + + Collection actualValues = map.values(); + assertEquals(size, actualValues.size()); + assertFalse(actualValues.isEmpty()); + + Set expectedValues = expectedValues(size); + for (Object value : map.values()) { + assertTrue(expectedValues.remove(value)); + } + assertTrue(expectedValues.isEmpty()); + } + static final int randomSize() { return ThreadLocalRandom.current().nextInt(MANY_SIZE); } @@ -283,6 +333,22 @@ static final TagMap createTagMap(int size) { return map; } + static final Set expectedKeys(int size) { + Set set = new HashSet(size); + for (int i = 0; i < size; ++i) { + set.add(key(i)); + } + return set; + } + + static final Set expectedValues(int size) { + Set set = new HashSet(size); + for (int i = 0; i < size; ++i) { + set.add(value(i)); + } + return set; + } + static final String key(int i) { return "key-" + i; } From 1915fd666cf3fec351d93f405cef6aa845dcc5b2 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 28 Mar 2025 11:26:37 -0400 Subject: [PATCH 60/84] Working on Entry test coverage Since TagMap.Entry is designed to be multi-threaded -- working towards a multi-threaded test Tests have now been restructured as a series of check function objects that are can be reordered to tests different orderings. The test then repeated multiple times to test a variety of orderings. This helps improve the test coverage as well. Next step is to add multi-threading testing --- .../datadog/trace/api/TagMapEntryTest.java | 493 +++++++++++------- 1 file changed, 306 insertions(+), 187 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 02c7acc3eb2..b753137d7df 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -4,284 +4,403 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import datadog.trace.api.TagMap.Entry; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; import org.junit.Test; +/** + * Since TagMap.Entry is thread safe and has involves complicated multi-thread type resolution code, + * this test uses a different approach to stress ordering different combinations. + * + *

Each test produces a series of check-s encapsulated in a Check object. + * + *

Those checks are then shuffled to simulate different operation orderings - both in single + * threaded and multi-threaded scenarios. + * + * @author dougqh + */ public class TagMapEntryTest { @Test public void objectEntry() { - TagMap.Entry entry = TagMap.Entry.newObjectEntry("foo", "bar"); - assertEquals(TagMap.Entry.OBJECT, entry.rawType); - - assertKey("foo", entry); - assertValue("bar", entry); - - assertEquals("bar", entry.stringValue()); - - assertTrue(entry.isObject()); + test( + () -> TagMap.Entry.newObjectEntry("foo", "bar"), + TagMap.Entry.OBJECT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue("bar", entry), + checkEquals("bar", entry::stringValue), + checkTrue(entry::isObject))); } @Test public void anyEntry_object() { - TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", "bar"); - assertEquals(TagMap.Entry.ANY, entry.rawType); - - assertKey("foo", entry); - assertValue("bar", entry); - - assertTrue(entry.isObject()); - - assertKey("foo", entry); - assertValue("bar", entry); + test( + () -> TagMap.Entry.newAnyEntry("foo", "bar"), + TagMap.Entry.ANY, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue("bar", entry), + checkTrue(entry::isObject), + checkKey("foo", entry), + checkValue("bar", entry))); } @Test public void booleanEntry() { - TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", true); - assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); - - assertKey("foo", entry); - assertValue(true, entry); - - assertFalse(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + test( + () -> TagMap.Entry.newBooleanEntry("foo", true), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(true, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @Test public void booleanEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)); - assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); - - assertKey("foo", entry); - assertValue(true, entry); - - assertFalse(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.BOOLEAN)); + test( + () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(true, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @Test public void anyEntry_boolean() { - TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(true)); - assertEquals(TagMap.Entry.ANY, entry.rawType); - - assertKey("foo", entry); - assertValue(true, entry); - - assertFalse(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.BOOLEAN)); - - assertValue(true, entry); + test( + () -> TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(true)), + TagMap.Entry.ANY, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(true, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry), + checkValue(true, entry))); } @Test public void intEntry() { - TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", 20); - assertEquals(TagMap.Entry.INT, entry.rawType); - - assertKey("foo", entry); - assertValue(20, entry); - - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.INT)); + test( + () -> TagMap.Entry.newIntEntry("foo", 20), + TagMap.Entry.INT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(20, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.INT, entry))); } @Test public void intEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)); - assertEquals(TagMap.Entry.INT, entry.rawType); - - assertKey("foo", entry); - assertValue(20, entry); - - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.INT)); + test( + () -> TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)), + TagMap.Entry.INT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(20, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.INT, entry))); } @Test public void anyEntry_int() { - TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)); - assertEquals(TagMap.Entry.ANY, entry.rawType); - - assertKey("foo", entry); - assertValue(20, entry); - - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.INT)); - - assertValue(20, entry); + test( + () -> TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)), + TagMap.Entry.ANY, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(20, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.INT, entry), + checkValue(20, entry))); } @Test public void longEntry() { - TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", 1_048_576L); - assertEquals(TagMap.Entry.LONG, entry.rawType); - - assertKey("foo", entry); - assertValue(1_048_576L, entry); - - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.LONG)); + test( + () -> TagMap.Entry.newLongEntry("foo", 1_048_576L), + TagMap.Entry.LONG, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(1_048_576L, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.LONG, entry))); } @Test public void longEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)); - assertEquals(TagMap.Entry.LONG, entry.rawType); - - assertKey("foo", entry); - assertValue(1_048_576L, entry); - - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.LONG)); + test( + () -> TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)), + TagMap.Entry.LONG, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(1_048_576L, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.LONG, entry))); } @Test public void anyEntry_long() { - TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)); - assertEquals(TagMap.Entry.ANY, entry.rawType); - - assertKey("foo", entry); - assertValue(1_048_576L, entry); - - // type checks force any resolution - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.LONG)); - - // check value again after resolution - assertValue(1_048_576L, entry); + test( + () -> TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)), + TagMap.Entry.ANY, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(1_048_576L, entry), + checkTrue(entry::isNumericPrimitive), + checkTrue(() -> entry.is(TagMap.Entry.LONG)), + checkValue(1_048_576L, entry))); } @Test public void doubleEntry() { - TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Math.PI); - assertEquals(TagMap.Entry.DOUBLE, entry.rawType); - - assertKey("foo", entry); - assertValue(Math.PI, entry); - - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.DOUBLE)); + test( + () -> TagMap.Entry.newDoubleEntry("foo", Math.PI), + TagMap.Entry.DOUBLE, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(Math.PI, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.DOUBLE, entry))); } @Test public void doubleEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)); - assertEquals(TagMap.Entry.DOUBLE, entry.rawType); - - assertKey("foo", entry); - assertValue(Math.PI, entry); - - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.DOUBLE)); + test( + () -> TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)), + TagMap.Entry.DOUBLE, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(Math.PI, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.DOUBLE, entry))); } @Test public void anyEntry_double() { - TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)); - assertEquals(TagMap.Entry.ANY, entry.rawType); - - assertKey("foo", entry); - assertValue(Math.PI, entry); + test( + () -> TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)), + TagMap.Entry.ANY, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(Math.PI, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.DOUBLE, entry), + checkValue(Math.PI, entry))); + } - // type checks force any resolution - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.DOUBLE)); + @Test + public void floatEntry() { + test( + () -> TagMap.Entry.newFloatEntry("foo", 2.718281828f), + TagMap.Entry.FLOAT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(2.718281828f, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.FLOAT, entry))); + } - // check value again after resolution - assertValue(Math.PI, entry); + @Test + public void floatEntry_boxed() { + test( + () -> TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)), + TagMap.Entry.FLOAT, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(2.718281828f, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.FLOAT, entry))); } @Test - public void floatEntry() { - TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", 2.718281828f); - assertEquals(TagMap.Entry.FLOAT, entry.rawType); + public void anyEntry_float() { + test( + () -> TagMap.Entry.newAnyEntry("foo", Float.valueOf(2.718281828f)), + TagMap.Entry.ANY, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(2.718281828f, entry), + checkTrue(entry::isNumericPrimitive), + checkType(TagMap.Entry.FLOAT, entry))); + } - assertKey("foo", entry); - assertValue(2.718281828f, entry); + static final void test( + Supplier entrySupplier, byte rawType, Function checks) { + // repeat the test several times to exercise different orderings + for (int i = 0; i < 10; ++i) { + Entry entry = entrySupplier.get(); + assertEquals(rawType, entry.rawType); - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.FLOAT)); + assertChecks(checks.apply(entry)); + } } - @Test - public void floatEntry_boxed() { - TagMap.Entry entry = TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)); - assertEquals(TagMap.Entry.FLOAT, entry.rawType); + static final void assertChecks(Check... checks) { + assertChecks(multiCheck(checks)); + } + + static final void assertChecks(Check check) { + check.check(); + } - assertKey("foo", entry); - assertValue(2.718281828f, entry); + static final Check checkKey(String expected, TagMap.Entry entry) { + return multiCheck(checkEquals(expected, entry::tag), checkEquals(expected, entry::getKey)); + } - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.FLOAT)); + static final Check checkValue(Object expected, TagMap.Entry entry) { + return multiCheck( + checkEquals(expected, entry::objectValue), + checkEquals(expected, entry::getValue), + checkEquals(expected.toString(), entry::stringValue)); } - @Test - public void anyEntry_float() { - TagMap.Entry entry = TagMap.Entry.newAnyEntry("foo", Float.valueOf(2.718281828f)); - assertEquals(TagMap.Entry.ANY, entry.rawType); + static final Check checkValue(boolean expected, TagMap.Entry entry) { + return multiCheck( + checkEquals(expected, entry::booleanValue), + checkEquals(Boolean.valueOf(expected), entry::objectValue), + checkEquals(Boolean.toString(expected), entry::stringValue)); + } - assertKey("foo", entry); - assertValue(2.718281828f, entry); + static final Check checkValue(int expected, TagMap.Entry entry) { + return multiCheck( + checkEquals(expected, entry::intValue), + checkEquals((long) expected, entry::longValue), + checkEquals((float) expected, entry::floatValue), + checkEquals((double) expected, entry::doubleValue), + checkEquals(Integer.valueOf(expected), entry::objectValue), + checkEquals(Integer.toString(expected), entry::stringValue)); + } - assertTrue(entry.isNumericPrimitive()); - assertTrue(entry.is(TagMap.Entry.FLOAT)); + static final Check checkValue(long expected, TagMap.Entry entry) { + return multiCheck( + checkEquals(expected, entry::longValue), + checkEquals((int) expected, entry::intValue), + checkEquals((float) expected, entry::floatValue), + checkEquals((double) expected, entry::doubleValue), + checkEquals(Long.valueOf(expected), entry::objectValue), + checkEquals(Long.toString(expected), entry::stringValue)); } - static final void assertKey(String expected, TagMap.Entry entry) { - assertEquals(expected, entry.tag()); - assertEquals(expected, entry.getKey()); + static final Check checkValue(double expected, TagMap.Entry entry) { + return multiCheck( + checkEquals(expected, entry::doubleValue), + checkEquals((int) expected, entry::intValue), + checkEquals((long) expected, entry::longValue), + checkEquals((float) expected, entry::floatValue), + checkEquals(Double.valueOf(expected), entry::objectValue), + checkEquals(Double.toString(expected), entry::stringValue)); } - static final void assertValue(Object expected, TagMap.Entry entry) { - assertEquals(expected, entry.objectValue()); - assertEquals(expected, entry.getValue()); + static final Check checkValue(float expected, TagMap.Entry entry) { + return multiCheck( + checkEquals(expected, entry::floatValue), + checkEquals((int) expected, entry::intValue), + checkEquals((long) expected, entry::longValue), + checkEquals((double) expected, entry::doubleValue), + checkEquals(Float.valueOf(expected), entry::objectValue), + checkEquals(Float.toString(expected), entry::stringValue)); + } - assertEquals(expected.toString(), entry.stringValue()); + static final Check checkType(byte entryType, TagMap.Entry entry) { + return () -> assertTrue(entry.is(entryType), "type is " + entryType); } - static final void assertValue(boolean expected, TagMap.Entry entry) { - assertEquals(expected, entry.booleanValue()); - assertEquals(Boolean.valueOf(expected), entry.objectValue()); + static final Check multiCheck(Check... checks) { + return new MultipartCheck(checks); + } - assertEquals(Boolean.toString(expected), entry.stringValue()); + static final Check checkFalse(Supplier actual) { + return () -> assertFalse(actual.get(), actual.toString()); } - static final void assertValue(int expected, TagMap.Entry entry) { - assertEquals(expected, entry.intValue()); - assertEquals((long) expected, entry.longValue()); - assertEquals((float) expected, entry.floatValue()); - assertEquals((double) expected, entry.doubleValue()); - assertEquals(Integer.valueOf(expected), entry.objectValue()); + static final Check checkTrue(Supplier actual) { + return () -> assertTrue(actual.get(), actual.toString()); + } - assertEquals(Integer.toString(expected), entry.stringValue()); + static final Check checkEquals(float expected, Supplier actual) { + return () -> assertEquals(expected, actual.get(), actual.toString()); } - static final void assertValue(long expected, TagMap.Entry entry) { - assertEquals(expected, entry.longValue()); - assertEquals((int) expected, entry.intValue()); - assertEquals((float) expected, entry.floatValue()); - assertEquals((double) expected, entry.doubleValue()); - assertEquals(Long.valueOf(expected), entry.objectValue()); + static final Check checkEquals(int expected, Supplier actual) { + return () -> assertEquals(expected, actual.get(), actual.toString()); + } - assertEquals(Long.toString(expected), entry.stringValue()); + static final Check checkEquals(double expected, Supplier actual) { + return () -> assertEquals(expected, actual.get(), actual.toString()); } - static final void assertValue(double expected, TagMap.Entry entry) { - assertEquals(expected, entry.doubleValue()); - assertEquals((int) expected, entry.intValue()); - assertEquals((long) expected, entry.longValue()); - assertEquals((float) expected, entry.floatValue()); - assertEquals(Double.valueOf(expected), entry.objectValue()); + static final Check checkEquals(long expected, Supplier actual) { + return () -> assertEquals(expected, actual.get(), actual.toString()); + } - assertEquals(Double.toString(expected), entry.stringValue()); + static final Check checkEquals(boolean expected, Supplier actual) { + return () -> assertEquals(expected, actual.get(), actual.toString()); } - static final void assertValue(float expected, TagMap.Entry entry) { - assertEquals(expected, entry.floatValue()); - assertEquals((int) expected, entry.intValue()); - assertEquals((long) expected, entry.longValue()); - assertEquals((double) expected, entry.doubleValue()); - assertEquals(Float.valueOf(expected), entry.objectValue()); + static final Check checkEquals(Object expected, Supplier actual) { + return () -> assertEquals(expected, actual.get(), actual.toString()); + } + + @FunctionalInterface + interface Check { + void check(); + + default void flatten(List checkAccumulator) { + checkAccumulator.add(this); + } + } - assertEquals(Float.toString(expected), entry.stringValue()); + static final class MultipartCheck implements Check { + private final Check[] checks; + + MultipartCheck(Check... checks) { + this.checks = checks; + } + + @Override + public void check() { + List checkAccumulator = new ArrayList<>(); + for (Check check : this.checks) { + check.flatten(checkAccumulator); + } + + Collections.shuffle(checkAccumulator); + for (Check check : checkAccumulator) { + check.check(); + } + } + + @Override + public void flatten(List checkAccumulator) { + for (Check check : this.checks) { + check.flatten(checkAccumulator); + } + } } } From 6335a475dc02e814a0b5fda5d3578dfe20e00e03 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 28 Mar 2025 13:52:58 -0400 Subject: [PATCH 61/84] Improving TagMap.Entry coverage - covering boolean conversions Covering boolean conversions, I neglected them in tests previously because I don't think they'll be used much. --- .../test/java/datadog/trace/api/TagMapEntryTest.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index b753137d7df..0fc5341038d 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -261,10 +261,6 @@ static final void test( } } - static final void assertChecks(Check... checks) { - assertChecks(multiCheck(checks)); - } - static final void assertChecks(Check check) { check.check(); } @@ -284,6 +280,10 @@ static final Check checkValue(boolean expected, TagMap.Entry entry) { return multiCheck( checkEquals(expected, entry::booleanValue), checkEquals(Boolean.valueOf(expected), entry::objectValue), + checkEquals(expected ? 1 : 0, entry::intValue), + checkEquals(expected ? 1L : 0L, entry::longValue), + checkEquals(expected ? 1D : 0D, entry::doubleValue), + checkEquals(expected ? 1F : 0F, entry::floatValue), checkEquals(Boolean.toString(expected), entry::stringValue)); } @@ -294,6 +294,7 @@ static final Check checkValue(int expected, TagMap.Entry entry) { checkEquals((float) expected, entry::floatValue), checkEquals((double) expected, entry::doubleValue), checkEquals(Integer.valueOf(expected), entry::objectValue), + checkEquals(expected != 0, entry::booleanValue), checkEquals(Integer.toString(expected), entry::stringValue)); } @@ -304,6 +305,7 @@ static final Check checkValue(long expected, TagMap.Entry entry) { checkEquals((float) expected, entry::floatValue), checkEquals((double) expected, entry::doubleValue), checkEquals(Long.valueOf(expected), entry::objectValue), + checkEquals(expected != 0L, entry::booleanValue), checkEquals(Long.toString(expected), entry::stringValue)); } @@ -314,6 +316,7 @@ static final Check checkValue(double expected, TagMap.Entry entry) { checkEquals((long) expected, entry::longValue), checkEquals((float) expected, entry::floatValue), checkEquals(Double.valueOf(expected), entry::objectValue), + checkEquals(expected != 0D, entry::booleanValue), checkEquals(Double.toString(expected), entry::stringValue)); } @@ -323,6 +326,7 @@ static final Check checkValue(float expected, TagMap.Entry entry) { checkEquals((int) expected, entry::intValue), checkEquals((long) expected, entry::longValue), checkEquals((double) expected, entry::doubleValue), + checkEquals(expected != 0F, entry::booleanValue), checkEquals(Float.valueOf(expected), entry::objectValue), checkEquals(Float.toString(expected), entry::stringValue)); } From c502dc388411ab80afa774dbef617bb41b438b74 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 28 Mar 2025 14:32:00 -0400 Subject: [PATCH 62/84] More TagMap coverage - adding fillMap - adding fillStringMap - adding primitive entry coverage --- .../java/datadog/trace/api/TagMapTest.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 11737e82508..a37cfd74f14 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; @@ -32,6 +33,76 @@ public void map_put() { assertNotEmpty(map); } + @Test + public void booleanEntry() { + TagMap map = new TagMap(); + map.set("bool", false); + + TagMap.Entry entry = map.getEntry("bool"); + assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); + + assertEquals(false, entry.booleanValue()); + assertEquals(false, map.getBoolean("bool")); + } + + @Test + public void intEntry() { + TagMap map = new TagMap(); + map.set("int", 42); + + TagMap.Entry entry = map.getEntry("int"); + assertEquals(TagMap.Entry.INT, entry.rawType); + + assertEquals(42, entry.intValue()); + assertEquals(42, map.getInt("int")); + } + + @Test + public void longEntry() { + TagMap map = new TagMap(); + map.set("long", 42L); + + TagMap.Entry entry = map.getEntry("long"); + assertEquals(TagMap.Entry.LONG, entry.rawType); + + assertEquals(42L, entry.longValue()); + assertEquals(42L, map.getLong("long")); + } + + @Test + public void floatEntry() { + TagMap map = new TagMap(); + map.set("float", 3.14F); + + TagMap.Entry entry = map.getEntry("float"); + assertEquals(TagMap.Entry.FLOAT, entry.rawType); + + assertEquals(3.14F, entry.floatValue()); + assertEquals(3.14F, map.getFloat("float")); + } + + @Test + public void doubleEntry() { + TagMap map = new TagMap(); + map.set("double", Math.PI); + + TagMap.Entry entry = map.getEntry("double"); + assertEquals(TagMap.Entry.DOUBLE, entry.rawType); + + assertEquals(Math.PI, entry.doubleValue()); + assertEquals(Math.PI, map.getDouble("double")); + } + + @Test + public void empty() { + TagMap empty = TagMap.EMPTY; + assertFrozen(empty); + + assertNull(empty.getEntry("foo")); + assertSize(0, empty); + assertEmpty(empty); + } + @Test public void clear() { int size = randomSize(); @@ -198,6 +269,40 @@ public void removeMany() { assertEmpty(map); } + @Test + public void fillMap() { + int size = randomSize(); + TagMap map = new TagMap(); + for (int i = 0; i < size; ++i) { + map.set(key(i), i); + } + + HashMap hashMap = new HashMap<>(); + map.fillMap(hashMap); + + for (int i = 0; i < size; ++i) { + assertEquals(Integer.valueOf(i), hashMap.remove(key(i))); + } + assertTrue(hashMap.isEmpty()); + } + + @Test + public void fillStringMap() { + int size = randomSize(); + TagMap map = new TagMap(); + for (int i = 0; i < size; ++i) { + map.set(key(i), i); + } + + HashMap hashMap = new HashMap<>(); + map.fillStringMap(hashMap); + + for (int i = 0; i < size; ++i) { + assertEquals(Integer.toString(i), hashMap.remove(key(i))); + } + assertTrue(hashMap.isEmpty()); + } + @Test public void iterator() { int size = randomSize(); From f7eee6707fe2665ed5cbca65b233924aa43cb591 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 28 Mar 2025 15:24:48 -0400 Subject: [PATCH 63/84] Added multi-threaded tests for TagMap.Entry --- .../datadog/trace/api/TagMapEntryTest.java | 113 ++++++++++++++++-- 1 file changed, 106 insertions(+), 7 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 0fc5341038d..33ee0d7792a 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -8,6 +8,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import java.util.function.Function; import java.util.function.Supplier; import org.junit.Test; @@ -250,14 +256,85 @@ public void anyEntry_float() { checkType(TagMap.Entry.FLOAT, entry))); } + static final int NUM_THREADS = 4; + static final ExecutorService EXECUTOR = + Executors.newFixedThreadPool( + NUM_THREADS, + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "multithreaded-test-runner"); + thread.setDaemon(true); + return thread; + } + }); + static final void test( Supplier entrySupplier, byte rawType, Function checks) { - // repeat the test several times to exercise different orderings + // repeat the test several times to exercise different orderings in this thread for (int i = 0; i < 10; ++i) { - Entry entry = entrySupplier.get(); - assertEquals(rawType, entry.rawType); + testSingleThreaded(entrySupplier, rawType, checks); + } - assertChecks(checks.apply(entry)); + // same for multi-threaded + for (int i = 0; i < 5; ++i) { + testMultiThreaded(entrySupplier, rawType, checks); + } + } + + static final void testSingleThreaded( + Supplier entrySupplier, byte rawType, Function checkSupplier) { + Entry entry = entrySupplier.get(); + assertEquals(rawType, entry.rawType); + + Check checks = checkSupplier.apply(entry); + checks.check(); + } + + static final void testMultiThreaded( + Supplier entrySupplier, byte rawType, Function checkSupplier) { + Entry sharedEntry = entrySupplier.get(); + assertEquals(rawType, sharedEntry.rawType); + + Check checks = checkSupplier.apply(sharedEntry); + + List> callables = new ArrayList<>(NUM_THREADS); + for (int i = 0; i < NUM_THREADS; ++i) { + // Different shuffle for each thread + Check shuffledChecks = checks.shuffle(); + + callables.add( + () -> { + shuffledChecks.check(); + + return null; + }); + } + + List> futures; + try { + futures = EXECUTOR.invokeAll(callables); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + + throw new IllegalStateException(e); + } + + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof Error) { + throw (Error) cause; + } else if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else { + throw new IllegalStateException(cause); + } + } } } @@ -375,6 +452,10 @@ static final Check checkEquals(Object expected, Supplier actual) { interface Check { void check(); + default Check shuffle() { + return this; + } + default void flatten(List checkAccumulator) { checkAccumulator.add(this); } @@ -387,19 +468,37 @@ static final class MultipartCheck implements Check { this.checks = checks; } - @Override - public void check() { + private List shuffleChecks() { List checkAccumulator = new ArrayList<>(); for (Check check : this.checks) { check.flatten(checkAccumulator); } Collections.shuffle(checkAccumulator); - for (Check check : checkAccumulator) { + return checkAccumulator; + } + + @Override + public void check() { + for (Check check : this.shuffleChecks()) { check.check(); } } + @Override + public Check shuffle() { + List shuffled = this.shuffleChecks(); + + return new Check() { + @Override + public void check() { + for (Check check : shuffled) { + check.check(); + } + } + }; + } + @Override public void flatten(List checkAccumulator) { for (Check check : this.checks) { From 5e15723c57bf247bf5ddcf2bcdfe5cc93ee72eb1 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 28 Mar 2025 15:29:37 -0400 Subject: [PATCH 64/84] Adding test for removal entry --- .../src/test/java/datadog/trace/api/TagMapEntryTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 33ee0d7792a..362c2d79ffd 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -1,5 +1,6 @@ package datadog.trace.api; +import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -256,6 +257,14 @@ public void anyEntry_float() { checkType(TagMap.Entry.FLOAT, entry))); } + @Test + public void removalEntry() { + TagMap.Entry removalEntry = TagMap.Entry.newRemovalEntry("foo"); + assertTrue(removalEntry.isRemoval()); + + assertNull(removalEntry.objectValue()); + } + static final int NUM_THREADS = 4; static final ExecutorService EXECUTOR = Executors.newFixedThreadPool( From 34ed23d0a89a7c86bba77e693e303b0b5aab15ba Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 31 Mar 2025 10:13:03 -0400 Subject: [PATCH 65/84] Working on TagMapTest coverage - added test for toString - fixed size selection to always pick something non-empty --- .../src/main/java/datadog/trace/api/TagMap.java | 2 +- .../src/test/java/datadog/trace/api/TagMapTest.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 818a8686bc0..a20f8faf26c 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -764,7 +764,7 @@ final String toPrettyString() { if (first) { first = false; } else { - builder.append(","); + builder.append(", "); } builder.append(entry.tag).append('=').append(entry.stringValue()); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index a37cfd74f14..30a88cd89ef 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -422,8 +422,17 @@ public void values() { assertTrue(expectedValues.isEmpty()); } + @Test + public void _toString() { + // DQH - This test assumes an iteration order which isn't guaranteed. Might need to be updated + // in the future. + int size = 4; + TagMap map = createTagMap(size); + assertEquals("{key-1=value-1, key-0=value-0, key-3=value-3, key-2=value-2}", map.toString()); + } + static final int randomSize() { - return ThreadLocalRandom.current().nextInt(MANY_SIZE); + return ThreadLocalRandom.current().nextInt(1, MANY_SIZE); } static final TagMap createTagMap() { From 93cb23a9b0ceb39b73b3c993dfba4b55988763eb Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 31 Mar 2025 10:18:24 -0400 Subject: [PATCH 66/84] Improving coverage - added "test" for toInternalString --- .../src/test/java/datadog/trace/api/TagMapTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 30a88cd89ef..d549d2119cb 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertSame; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collection; @@ -424,13 +425,17 @@ public void values() { @Test public void _toString() { - // DQH - This test assumes an iteration order which isn't guaranteed. Might need to be updated - // in the future. int size = 4; TagMap map = createTagMap(size); assertEquals("{key-1=value-1, key-0=value-0, key-3=value-3, key-2=value-2}", map.toString()); } + @Test + public void toInternalString() { + TagMap map = createTagMap(128); + assertNotEquals("", map.toInternalString()); + } + static final int randomSize() { return ThreadLocalRandom.current().nextInt(1, MANY_SIZE); } From a4aaa71a91bc7d31bde04aeb83fe3addb04d0e9b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 31 Mar 2025 14:00:06 -0400 Subject: [PATCH 67/84] Appease spotbugs --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index a20f8faf26c..5ddaef281cd 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1068,6 +1068,10 @@ public final Object objectValue() { case DOUBLE: this.rawObj = prim2Double(this.rawPrim); break; + + default: + // DQH - satisfy spot bugs + break; } if (this.is(REMOVED)) { From 63b5a2e33c187499969ff670ad257f2018f7c61e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 7 Apr 2025 12:50:37 -0400 Subject: [PATCH 68/84] Increasing bucket size to 32 - provides better results under "real" workloads --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 5ddaef281cd..4ea5e27d055 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -104,7 +104,7 @@ public static final TagMap fromMapImmutable(Map map) { public TagMap() { // needs to be a power of 2 for bucket masking calculation to work as intended - this.buckets = new Object[1 << 4]; + this.buckets = new Object[1 << 5]; this.frozen = false; } From 04c48d814fd044ffac4dda5aa604f2d98a564b3d Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Wed, 23 Apr 2025 14:40:01 -0400 Subject: [PATCH 69/84] Smaller array for EMPTY instance Addressing review comments, using smaller array for EMPTY instance Given that EMPTY now breaks the rules of fixed number of buckets, added an extra test to make sure putAll EMPTY works as expected --- .../src/main/java/datadog/trace/api/TagMap.java | 5 ++++- .../src/test/java/datadog/trace/api/TagMapTest.java | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 4ea5e27d055..fcf1c43354f 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -70,7 +70,10 @@ public final class TagMap implements Map, Iterable public static final TagMap EMPTY = createEmpty(); private static final TagMap createEmpty() { - return new TagMap().freeze(); + // Using special constructor that creates a frozen view of an existing array + // Bucket calculation requires that array length is a power of 2 + // e.g. size 0 will not work, it results in ArrayIndexOutOfBoundsException, but size 1 does + return new TagMap(new Object[1]); } /** Creates a new TagMap.Builder */ diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index d549d2119cb..2e8b4796ce0 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -104,6 +104,17 @@ public void empty() { assertEmpty(empty); } + @Test + public void putAll_empty() { + // TagMap.EMPTY breaks the rules and uses a different size bucket array + // This test is just to verify that the commonly use putAll still works with EMPTY + TagMap newMap = new TagMap(); + newMap.putAll(TagMap.EMPTY); + + assertSize(0, newMap); + assertEmpty(newMap); + } + @Test public void clear() { int size = randomSize(); From e3d1fafe3b6ef017722bdb8d1783fd39e9475680 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 24 Apr 2025 16:10:15 -0400 Subject: [PATCH 70/84] More tests Parameterized TagMapEntryTest making sure to check negatives and positives, maxes and mins for all primitive related tests --- .../java/datadog/trace/api/TagMapEntryTest.java | 9 +++++++-- .../test/java/datadog/trace/api/TagMapTest.java | 14 +++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 362c2d79ffd..01223162016 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -60,13 +60,18 @@ public void anyEntry_object() { @Test public void booleanEntry() { + testBoolean(false); + testBoolean(true); + } + + final void testBoolean(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", true), + () -> TagMap.Entry.newBooleanEntry("foo", value), TagMap.Entry.BOOLEAN, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(true, entry), + checkValue(value, entry), checkFalse(entry::isNumericPrimitive), checkType(TagMap.Entry.BOOLEAN, entry))); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 2e8b4796ce0..8f9ac0df145 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -106,13 +106,13 @@ public void empty() { @Test public void putAll_empty() { - // TagMap.EMPTY breaks the rules and uses a different size bucket array - // This test is just to verify that the commonly use putAll still works with EMPTY - TagMap newMap = new TagMap(); - newMap.putAll(TagMap.EMPTY); - - assertSize(0, newMap); - assertEmpty(newMap); + // TagMap.EMPTY breaks the rules and uses a different size bucket array + // This test is just to verify that the commonly use putAll still works with EMPTY + TagMap newMap = new TagMap(); + newMap.putAll(TagMap.EMPTY); + + assertSize(0, newMap); + assertEmpty(newMap); } @Test From 24af07865e4575c93727820160bb9dd4530eadb4 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 24 Apr 2025 16:11:57 -0400 Subject: [PATCH 71/84] More tests Parameterized TagMapEntryTest making sure to check negatives and positives, maxes and mins for all primitive related tests --- .../datadog/trace/api/TagMapEntryTest.java | 201 +++++++++--------- 1 file changed, 106 insertions(+), 95 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 01223162016..8be47f54b32 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -18,6 +18,8 @@ import java.util.function.Function; import java.util.function.Supplier; import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** * Since TagMap.Entry is thread safe and has involves complicated multi-thread type resolution code, @@ -58,208 +60,217 @@ public void anyEntry_object() { checkValue("bar", entry))); } - @Test - public void booleanEntry() { - testBoolean(false); - testBoolean(true); - } - - final void testBoolean(boolean value) { + @ParameterizedTest + @ValueSource(booleans={false, true}) + public void booleanEntry(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", value), - TagMap.Entry.BOOLEAN, - (entry) -> - multiCheck( - checkKey("foo", entry), - checkValue(value, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); - } - - @Test - public void booleanEntry_boxed() { + () -> TagMap.Entry.newBooleanEntry("foo", value), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); + } + + @ParameterizedTest + @ValueSource(booleans= {false, true}) + public void booleanEntry_boxed(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(true)), - TagMap.Entry.BOOLEAN, - (entry) -> - multiCheck( - checkKey("foo", entry), - checkValue(true, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); - } - - @Test - public void anyEntry_boolean() { + () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(value)), + TagMap.Entry.BOOLEAN, + (entry) -> multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); + } + + @ParameterizedTest + @ValueSource(booleans= {false, true}) + public void anyEntry_boolean(boolean value) { test( - () -> TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(true)), + () -> TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(value)), TagMap.Entry.ANY, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(true, entry), + checkValue(value, entry), checkFalse(entry::isNumericPrimitive), checkType(TagMap.Entry.BOOLEAN, entry), - checkValue(true, entry))); + checkValue(value, entry))); } - @Test - public void intEntry() { + @ParameterizedTest + @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + public void intEntry(int value) { test( - () -> TagMap.Entry.newIntEntry("foo", 20), + () -> TagMap.Entry.newIntEntry("foo", value), TagMap.Entry.INT, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(20, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.INT, entry))); } - @Test - public void intEntry_boxed() { + @ParameterizedTest + @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + public void intEntry_boxed(int value) { test( - () -> TagMap.Entry.newIntEntry("foo", Integer.valueOf(20)), + () -> TagMap.Entry.newIntEntry("foo", Integer.valueOf(value)), TagMap.Entry.INT, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(20, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.INT, entry))); } - @Test - public void anyEntry_int() { + @ParameterizedTest + @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + public void anyEntry_int(int value) { test( - () -> TagMap.Entry.newAnyEntry("foo", Integer.valueOf(20)), + () -> TagMap.Entry.newAnyEntry("foo", Integer.valueOf(value)), TagMap.Entry.ANY, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(20, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.INT, entry), - checkValue(20, entry))); + checkValue(value, entry))); } - @Test - public void longEntry() { + @ParameterizedTest + @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + public void longEntry(long value) { test( - () -> TagMap.Entry.newLongEntry("foo", 1_048_576L), + () -> TagMap.Entry.newLongEntry("foo", value), TagMap.Entry.LONG, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(1_048_576L, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.LONG, entry))); } - @Test - public void longEntry_boxed() { + @ParameterizedTest + @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + public void longEntry_boxed(long value) { test( - () -> TagMap.Entry.newLongEntry("foo", Long.valueOf(1_048_576L)), + () -> TagMap.Entry.newLongEntry("foo", Long.valueOf(value)), TagMap.Entry.LONG, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(1_048_576L, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), checkType(TagMap.Entry.LONG, entry))); } - @Test - public void anyEntry_long() { + @ParameterizedTest + @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + public void anyEntry_long(long value) { test( - () -> TagMap.Entry.newAnyEntry("foo", Long.valueOf(1_048_576L)), + () -> TagMap.Entry.newAnyEntry("foo", Long.valueOf(value)), TagMap.Entry.ANY, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(1_048_576L, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), checkTrue(() -> entry.is(TagMap.Entry.LONG)), - checkValue(1_048_576L, entry))); + checkValue(value, entry))); } - @Test - public void doubleEntry() { + @ParameterizedTest + @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + public void floatEntry(float value) { test( - () -> TagMap.Entry.newDoubleEntry("foo", Math.PI), - TagMap.Entry.DOUBLE, + () -> TagMap.Entry.newFloatEntry("foo", value), + TagMap.Entry.FLOAT, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(Math.PI, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), - checkType(TagMap.Entry.DOUBLE, entry))); + checkType(TagMap.Entry.FLOAT, entry))); } - @Test - public void doubleEntry_boxed() { + @ParameterizedTest + @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + public void floatEntry_boxed(float value) { test( - () -> TagMap.Entry.newDoubleEntry("foo", Double.valueOf(Math.PI)), - TagMap.Entry.DOUBLE, + () -> TagMap.Entry.newFloatEntry("foo", Float.valueOf(value)), + TagMap.Entry.FLOAT, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(Math.PI, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), - checkType(TagMap.Entry.DOUBLE, entry))); + checkType(TagMap.Entry.FLOAT, entry))); } - @Test - public void anyEntry_double() { + @ParameterizedTest + @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + public void anyEntry_float(float value) { test( - () -> TagMap.Entry.newAnyEntry("foo", Double.valueOf(Math.PI)), + () -> TagMap.Entry.newAnyEntry("foo", Float.valueOf(value)), TagMap.Entry.ANY, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(Math.PI, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), - checkType(TagMap.Entry.DOUBLE, entry), - checkValue(Math.PI, entry))); + checkType(TagMap.Entry.FLOAT, entry))); } - @Test - public void floatEntry() { + @ParameterizedTest + @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + public void doubleEntry(double value) { test( - () -> TagMap.Entry.newFloatEntry("foo", 2.718281828f), - TagMap.Entry.FLOAT, + () -> TagMap.Entry.newDoubleEntry("foo", value), + TagMap.Entry.DOUBLE, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(2.718281828f, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), - checkType(TagMap.Entry.FLOAT, entry))); + checkType(TagMap.Entry.DOUBLE, entry))); } - @Test - public void floatEntry_boxed() { + @ParameterizedTest + @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + public void doubleEntry_boxed(double value) { test( - () -> TagMap.Entry.newFloatEntry("foo", Float.valueOf(2.718281828f)), - TagMap.Entry.FLOAT, + () -> TagMap.Entry.newDoubleEntry("foo", Double.valueOf(value)), + TagMap.Entry.DOUBLE, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(2.718281828f, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), - checkType(TagMap.Entry.FLOAT, entry))); + checkType(TagMap.Entry.DOUBLE, entry))); } - @Test - public void anyEntry_float() { + @ParameterizedTest + @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + public void anyEntry_double(double value) { test( - () -> TagMap.Entry.newAnyEntry("foo", Float.valueOf(2.718281828f)), + () -> TagMap.Entry.newAnyEntry("foo", Double.valueOf(value)), TagMap.Entry.ANY, (entry) -> multiCheck( checkKey("foo", entry), - checkValue(2.718281828f, entry), + checkValue(value, entry), checkTrue(entry::isNumericPrimitive), - checkType(TagMap.Entry.FLOAT, entry))); + checkType(TagMap.Entry.DOUBLE, entry), + checkValue(value, entry))); } @Test From c5aa2448795b52dfab60cd394d49ade463581682 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 24 Apr 2025 16:12:35 -0400 Subject: [PATCH 72/84] spotless --- .../datadog/trace/api/TagMapEntryTest.java | 109 +++++++++++++----- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 8be47f54b32..d0be8fd0172 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -61,34 +61,35 @@ public void anyEntry_object() { } @ParameterizedTest - @ValueSource(booleans={false, true}) + @ValueSource(booleans = {false, true}) public void booleanEntry(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", value), - TagMap.Entry.BOOLEAN, - (entry) -> - multiCheck( - checkKey("foo", entry), - checkValue(value, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); + () -> TagMap.Entry.newBooleanEntry("foo", value), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @ParameterizedTest - @ValueSource(booleans= {false, true}) + @ValueSource(booleans = {false, true}) public void booleanEntry_boxed(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(value)), - TagMap.Entry.BOOLEAN, - (entry) -> multiCheck( - checkKey("foo", entry), - checkValue(value, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); + () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(value)), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @ParameterizedTest - @ValueSource(booleans= {false, true}) + @ValueSource(booleans = {false, true}) public void anyEntry_boolean(boolean value) { test( () -> TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(value)), @@ -103,7 +104,7 @@ public void anyEntry_boolean(boolean value) { } @ParameterizedTest - @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void intEntry(int value) { test( () -> TagMap.Entry.newIntEntry("foo", value), @@ -117,7 +118,7 @@ public void intEntry(int value) { } @ParameterizedTest - @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void intEntry_boxed(int value) { test( () -> TagMap.Entry.newIntEntry("foo", Integer.valueOf(value)), @@ -131,7 +132,7 @@ public void intEntry_boxed(int value) { } @ParameterizedTest - @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void anyEntry_int(int value) { test( () -> TagMap.Entry.newAnyEntry("foo", Integer.valueOf(value)), @@ -146,7 +147,22 @@ public void anyEntry_int(int value) { } @ParameterizedTest - @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) public void longEntry(long value) { test( () -> TagMap.Entry.newLongEntry("foo", value), @@ -160,7 +176,22 @@ public void longEntry(long value) { } @ParameterizedTest - @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) public void longEntry_boxed(long value) { test( () -> TagMap.Entry.newLongEntry("foo", Long.valueOf(value)), @@ -174,7 +205,22 @@ public void longEntry_boxed(long value) { } @ParameterizedTest - @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) public void anyEntry_long(long value) { test( () -> TagMap.Entry.newAnyEntry("foo", Long.valueOf(value)), @@ -189,7 +235,7 @@ public void anyEntry_long(long value) { } @ParameterizedTest - @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void floatEntry(float value) { test( () -> TagMap.Entry.newFloatEntry("foo", value), @@ -203,7 +249,7 @@ public void floatEntry(float value) { } @ParameterizedTest - @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void floatEntry_boxed(float value) { test( () -> TagMap.Entry.newFloatEntry("foo", Float.valueOf(value)), @@ -217,7 +263,7 @@ public void floatEntry_boxed(float value) { } @ParameterizedTest - @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void anyEntry_float(float value) { test( () -> TagMap.Entry.newAnyEntry("foo", Float.valueOf(value)), @@ -231,7 +277,8 @@ public void anyEntry_float(float value) { } @ParameterizedTest - @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void doubleEntry(double value) { test( () -> TagMap.Entry.newDoubleEntry("foo", value), @@ -245,7 +292,8 @@ public void doubleEntry(double value) { } @ParameterizedTest - @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void doubleEntry_boxed(double value) { test( () -> TagMap.Entry.newDoubleEntry("foo", Double.valueOf(value)), @@ -259,7 +307,8 @@ public void doubleEntry_boxed(double value) { } @ParameterizedTest - @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void anyEntry_double(double value) { test( () -> TagMap.Entry.newAnyEntry("foo", Double.valueOf(value)), From 266b36d20aa7a30477d62bca209b680ea133a682 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 25 Apr 2025 13:43:54 -0400 Subject: [PATCH 73/84] Updated TagMap to track size Adding size tracking to TagMap - reduced visibility of checkIfEmpty and computeSize - isEmpty and size are no longer deprecated - updated callers of computeSize to use size instead - adding more size checking into tests - added additional test for putAll with non-fully overlapping maps for source and destination - restored checkIntegrity method in TagMap - checkIntegrity is now called by TagMapFuzzTest after each step to further verify correctness --- .../tagprocessor/PayloadTagsProcessor.java | 4 +- .../main/java/datadog/trace/api/TagMap.java | 179 +++++++++++------- .../datadog/trace/api/TagMapFuzzTest.java | 2 + .../java/datadog/trace/api/TagMapTest.java | 33 +++- 4 files changed, 140 insertions(+), 78 deletions(-) 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 3d413d98b5f..00c299e36fe 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 @@ -72,7 +72,7 @@ public static PayloadTagsProcessor create(Config config) { @Override public void processTags( TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { - int spanMaxTags = maxTags + unsafeTags.computeSize(); + int spanMaxTags = maxTags + unsafeTags.size(); for (Map.Entry tagPrefixRedactionRules : redactionRulesByTagPrefix.entrySet()) { String tagPrefix = tagPrefixRedactionRules.getKey(); @@ -260,7 +260,7 @@ public boolean keepParsing(PathCursor path) { } public boolean keepCollectingTags() { - if (collectedTags.computeSize() < maxTags) { + if (collectedTags.size() < maxTags) { return true; } collectedTags.put(DD_PAYLOAD_TAGS_INCOMPLETE, true); diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index fcf1c43354f..14289fb0605 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -103,32 +103,29 @@ public static final TagMap fromMapImmutable(Map map) { } private final Object[] buckets; + private int size; private boolean frozen; public TagMap() { // needs to be a power of 2 for bucket masking calculation to work as intended - this.buckets = new Object[1 << 5]; + this.buckets = new Object[1 << 4]; + this.size = 0; this.frozen = false; } /** Used for inexpensive immutable */ private TagMap(Object[] buckets) { this.buckets = buckets; + this.size = 0; this.frozen = true; } - @Deprecated @Override public final int size() { - return this.computeSize(); + return this.size; } - /** - * Computes the size of the TagMap - * - *

computeSize is fast but is an O(n) operation. - */ - public final int computeSize() { + final int computeSize() { Object[] thisBuckets = this.buckets; int size = 0; @@ -145,18 +142,12 @@ public final int computeSize() { return size; } - @Deprecated @Override - public boolean isEmpty() { - return this.checkIfEmpty(); + public final boolean isEmpty() { + return (this.size == 0); } - - /** - * Checks if TagMap is empty - * - *

checkIfEmpty is fast but is an O(n) operation. - */ - public final boolean checkIfEmpty() { + + final boolean checkIfEmpty() { Object[] thisBuckets = this.buckets; for (int i = 0; i < thisBuckets.length; ++i) { @@ -337,17 +328,20 @@ public final Entry putEntry(Entry newEntry) { if (bucket == null) { thisBuckets[bucketIndex] = newEntry; + this.size += 1; return null; } else if (bucket instanceof Entry) { Entry existingEntry = (Entry) bucket; if (existingEntry.matches(newEntry.tag)) { thisBuckets[bucketIndex] = newEntry; + // replaced existing entry - no size change return existingEntry; } else { thisBuckets[bucketIndex] = new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry); + this.size += 1; return null; } } else if (bucket instanceof BucketGroup) { @@ -355,14 +349,18 @@ public final Entry putEntry(Entry newEntry) { BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag); if (containingGroup != null) { + // replaced existing entry - no size change return containingGroup._replace(newHash, newEntry); } if (!lastGroup.insertInChain(newHash, newEntry)) { thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup); } + this.size += 1; return null; } + + // unreachable return null; } @@ -394,6 +392,18 @@ private final void putAll(Entry[] tagEntries, int size) { } } + public final void putAll(Map map) { + this.checkWriteAccess(); + + if (map instanceof TagMap) { + this.putAll((TagMap) map); + } else { + for (Map.Entry entry : map.entrySet()) { + this.put(entry.getKey(), entry.getValue()); + } + } + } + /** * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another * @@ -422,10 +432,13 @@ public final void putAll(TagMap that) { if (thatBucket instanceof Entry) { thisBuckets[i] = thatBucket; + this.size += 1; } else if (thatBucket instanceof BucketGroup) { BucketGroup thatGroup = (BucketGroup) thatBucket; - thisBuckets[i] = thatGroup.cloneChain(); + BucketGroup thisNewGroup = thatGroup.cloneChain(); + thisBuckets[i] = thisNewGroup; + this.size += thisNewGroup.sizeInChain(); } } else if (thisBucket instanceof Entry) { // This bucket is a single entry, medium complexity case @@ -442,28 +455,39 @@ public final void putAll(TagMap that) { if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { thisBuckets[i] = thatEntry; + // replacing entry, no size change } else { thisBuckets[i] = new BucketGroup( thisHash, thisEntry, thatHash, thatEntry); + this.size += 1; } } else if (thatBucket instanceof BucketGroup) { BucketGroup thatGroup = (BucketGroup) thatBucket; // Clone the other group, then place this entry into that group BucketGroup thisNewGroup = thatGroup.cloneChain(); + int thisNewGroupSize = thisNewGroup.sizeInChain(); Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag()); if (incomingEntry != null) { - // there's already an entry w/ the same tag from the incoming TagMap - done + // there's already an entry w/ the same tag from the incoming TagMap + // incoming entry clobbers the existing try, so we're done thisBuckets[i] = thisNewGroup; + this.size += + thisNewGroupSize + - 1; // overlapping group - subtract one for clobbered existing entry } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) { // able to add thisEntry into the existing groups thisBuckets[i] = thisNewGroup; + this.size += + thisNewGroupSize; // non overlapping group - existing entry already accounted for } else { // unable to add into the existing groups thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); + this.size += + thisNewGroupSize; // non overlapping group - existing entry already accounted for } } } else if (thisBucket instanceof BucketGroup) { @@ -475,27 +499,27 @@ public final void putAll(TagMap that) { Entry thatEntry = (Entry) thatBucket; int thatHash = thatEntry.hash(); - if (!thisGroup.replaceOrInsertInChain(thatHash, thatEntry)) { + if (thisGroup.replaceInChain(thatHash, thatEntry) != null) { + // replaced existing entry no size change + } else if (thisGroup.insertInChain(thatHash, thatEntry)) { + this.size += 1; + } else { thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup); + this.size += 1; } } else if (thatBucket instanceof BucketGroup) { // Most complicated case - need to walk that bucket group chain and update this chain BucketGroup thatGroup = (BucketGroup) thatBucket; - thisBuckets[i] = thisGroup.replaceOrInsertAllInChain(thatGroup); - } - } - } - } + // Taking the easy / expensive way out for updating size + int thisPrevGroupSize = thisGroup.sizeInChain(); - public final void putAll(Map map) { - this.checkWriteAccess(); + BucketGroup thisNewGroup = thisGroup.replaceOrInsertAllInChain(thatGroup); + int thisNewGroupSize = thisNewGroup.sizeInChain(); - if (map instanceof TagMap) { - this.putAll((TagMap) map); - } else { - for (Map.Entry entry : map.entrySet()) { - this.put(entry.getKey(), entry.getValue()); + thisBuckets[i] = thisNewGroup; + this.size += (thisNewGroupSize - thisPrevGroupSize); + } } } } @@ -563,6 +587,8 @@ public final Entry removeEntry(String tag) { Entry existingEntry = (Entry) bucket; if (existingEntry.matches(tag)) { thisBuckets[bucketIndex] = null; + + this.size -= 1; return existingEntry; } else { return null; @@ -580,6 +606,7 @@ public final Entry removeEntry(String tag) { this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup); } + this.size -= 1; return existingEntry; } return null; @@ -697,6 +724,7 @@ public final void clear() { this.checkWriteAccess(); Arrays.fill(this.buckets, null); + this.size = 0; } /** Freeze the TagMap preventing further modification - returns this TagMap */ @@ -716,39 +744,50 @@ public final void checkWriteAccess() { if (this.frozen) throw new IllegalStateException("TagMap frozen"); } - // final void check() { - // Object[] thisBuckets = this.buckets; - // - // for ( int i = 0; i < thisBuckets.length; ++i ) { - // Object thisBucket = thisBuckets[i]; - // - // if ( thisBucket instanceof Entry ) { - // Entry thisEntry = (Entry)thisBucket; - // int thisHash = thisEntry.hash(); - // - // int expectedBucket = thisHash & (thisBuckets.length - 1); - // assert expectedBucket == i; - // } else if ( thisBucket instanceof BucketGroup ) { - // BucketGroup thisGroup = (BucketGroup)thisBucket; - // - // for ( BucketGroup curGroup = thisGroup; - // curGroup != null; - // curGroup = curGroup.prev ) - // { - // for ( int j = 0; j < BucketGroup.LEN; ++j ) { - // Entry thisEntry = curGroup._entryAt(i); - // if ( thisEntry == null ) continue; - // - // int thisHash = thisEntry.hash(); - // assert curGroup._hashAt(i) == thisHash; - // - // int expectedBucket = thisHash & (thisBuckets.length - 1); - // assert expectedBucket == i; - // } - // } - // } - // } - // } + final void checkIntegrity() { + Object[] thisBuckets = this.buckets; + + for ( int i = 0; i < thisBuckets.length; ++i ) { + Object thisBucket = thisBuckets[i]; + + if ( thisBucket instanceof Entry ) { + Entry thisEntry = (Entry)thisBucket; + int thisHash = thisEntry.hash(); + + int expectedBucket = thisHash & (thisBuckets.length - 1); + if ( expectedBucket != i ) { + throw new IllegalStateException("incorrect bucket"); + } + } else if ( thisBucket instanceof BucketGroup ) { + BucketGroup thisGroup = (BucketGroup)thisBucket; + + for ( BucketGroup curGroup = thisGroup; + curGroup != null; + curGroup = curGroup.prev ) + { + for ( int j = 0; j < BucketGroup.LEN; ++j ) { + Entry thisEntry = curGroup._entryAt(i); + if ( thisEntry == null ) continue; + + int thisHash = thisEntry.hash(); + assert curGroup._hashAt(i) == thisHash; + + int expectedBucket = thisHash & (thisBuckets.length - 1); + if ( expectedBucket != i ) { + throw new IllegalStateException("incorrect bucket"); + } + } + } + } + } + + if ( this.size != this.computeSize() ) { + throw new IllegalStateException("incorrect size"); + } + if ( this.isEmpty() != this.checkIfEmpty() ) { + throw new IllegalStateException("incorrect empty status"); + } + } @Override public final String toString() { @@ -1882,10 +1921,6 @@ BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) { return thisNewestHeadGroup; } - boolean replaceOrInsertInChain(int hash, Entry entry) { - return (this.replaceInChain(hash, entry) != null) || this.insertInChain(hash, entry); - } - Entry replaceInChain(int hash, Entry entry) { for (BucketGroup curGroup = this; curGroup != null; curGroup = curGroup.prev) { Entry prevEntry = curGroup._replace(hash, entry); @@ -2097,7 +2132,7 @@ public String toString() { StringBuilder builder = new StringBuilder(32); builder.append('['); for (int i = 0; i < BucketGroup.LEN; ++i) { - if (builder.length() != 0) builder.append(", "); + if (i != 0) builder.append(", "); builder.append(this._entryAt(i)); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index 91345c5c2ff..40579a25820 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -996,6 +996,8 @@ static final void assertMapEquals(Map expected, TagMap actual) { Object expectedValue = expected.get(actualEntry.tag()); assertEquals(expectedValue, actualEntry.objectValue()); } + + actual.checkIntegrity(); } static final String randomKey() { diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 8f9ac0df145..bc51605ba36 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -139,6 +139,7 @@ public void map_put_replacement() { assertNotEmpty(map); Object prev2 = map.put("foo", "baz"); + assertSize(1, map); assertEquals("bar", prev2); assertEntry("foo", "baz", map); @@ -203,9 +204,10 @@ public void putMany() { } @Test - public void cloneMany() { + public void copyMany() { int size = randomSize(); TagMap orig = createTagMap(size); + assertSize(size, orig); TagMap copy = orig.copy(); orig.clear(); // doing this to make sure that copied isn't modified @@ -213,6 +215,7 @@ public void cloneMany() { for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), copy); } + assertSize(size, copy); } @Test @@ -240,9 +243,10 @@ public void shareEntry() { } @Test - public void putAll() { - int size = 67; + public void putAll_clobberAll() { + int size = randomSize(); TagMap orig = createTagMap(size); + assertSize(size, orig); TagMap dest = new TagMap(); for (int i = size - 1; i >= 0; --i) { @@ -252,10 +256,31 @@ public void putAll() { // This should clobber all the values in dest dest.putAll(orig); - // assertSize(size, dest); for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), dest); } + assertSize(size, dest); + } + + @Test + public void putAll_clobberAndExtras() { + int size = randomSize(); + TagMap orig = createTagMap(size); + assertSize(size, orig); + + TagMap dest = new TagMap(); + for (int i = size / 2 - 1; i >= 0; --i) { + dest.set(key(i), altValue(i)); + } + + // This should clobber all the values in dest + dest.putAll(orig); + + for (int i = 0; i < size; ++i) { + assertEntry(key(i), value(i), dest); + } + + assertSize(size, dest); } @Test From 176846f203e18c4b325f4c4adad1b3c685fa3cb6 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 25 Apr 2025 16:17:20 -0400 Subject: [PATCH 74/84] Optimizing putAll into a new empty map Optimizing the common case of doing putAll into a new empty map / clone Moved existing putAll code into putAllMerge Added putAllIntoEmptyMap which is special case of putAll for the common empty destination case (used by clone) putAll now picks between putAllIntoEmptyMap and putAllMerge based on the map size --- .../main/java/datadog/trace/api/TagMap.java | 159 +++++++++++------- 1 file changed, 97 insertions(+), 62 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 14289fb0605..690d5e35bef 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -30,16 +30,10 @@ * primitive. By using immutable entries, the entry objects can be shared between builders & maps * freely. * - *

This map lacks some features of a regular java.util.Map... - * - *

    - *
  • Entry object mutation - *
  • size tracking - falls back to computeSize - *
  • manipulating Map through the entrySet() or values() - *
- * - *

Also lacks features designed for handling large maps... + *

This map lacks the ability to mutate an entry via @link {@link Entry#setValue(Object)}. + * Entries must be replaced by re-setting / re-putting the key which will create a new Entry object. * + *

This map also lacks features designed for handling large long lived mutable maps... *

    *
  • bucket array expansion *
  • adaptive collision @@ -52,7 +46,7 @@ * When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly. *

    * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain - * form a link list to handle collisions. + * directly form a link list to handle collisions. *

    * Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries. * But a BucketGroup is only formed when a collision occurs to keep allocation low in the common case of no collisions. @@ -61,7 +55,7 @@ * to hold the additional Entry-s. And the BucketGroup-s are connected via a linked list instead of the Entry-s. *

    * This does introduce some inefficiencies when Entry-s are removed. - * In the current system, given that removals are rare, BucketGroups are never consolidated. + * The assumption is that removals are rare, so BucketGroups are never consolidated. * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be * removed from the collision chain. */ @@ -125,44 +119,10 @@ public final int size() { return this.size; } - final int computeSize() { - Object[] thisBuckets = this.buckets; - - int size = 0; - for (int i = 0; i < thisBuckets.length; ++i) { - Object curBucket = thisBuckets[i]; - - if (curBucket instanceof Entry) { - size += 1; - } else if (curBucket instanceof BucketGroup) { - BucketGroup curGroup = (BucketGroup) curBucket; - size += curGroup.sizeInChain(); - } - } - return size; - } - @Override public final boolean isEmpty() { return (this.size == 0); } - - final boolean checkIfEmpty() { - Object[] thisBuckets = this.buckets; - - for (int i = 0; i < thisBuckets.length; ++i) { - Object curBucket = thisBuckets[i]; - - if (curBucket instanceof Entry) { - return false; - } else if (curBucket instanceof BucketGroup) { - BucketGroup curGroup = (BucketGroup) curBucket; - if (!curGroup.isEmptyChain()) return false; - } - } - - return true; - } @Override public boolean containsKey(Object key) { @@ -399,7 +359,8 @@ public final void putAll(Map map) { this.putAll((TagMap) map); } else { for (Map.Entry entry : map.entrySet()) { - this.put(entry.getKey(), entry.getValue()); + // use set which returns a prior Entry rather put which may box a prior primitive value + this.set(entry.getKey(), entry.getValue()); } } } @@ -412,21 +373,29 @@ public final void putAll(Map map) { * source TagMap */ public final void putAll(TagMap that) { - this.checkWriteAccess(); - + this.checkWriteAccess(); + + if ( this.size == 0 ) { + this.putAllIntoEmptyMap(that); + } else { + this.putAllMerge(that); + } + } + + private final void putAllMerge(TagMap that) { Object[] thisBuckets = this.buckets; Object[] thatBuckets = that.buckets; // Since TagMap-s don't support expansion, buckets are perfectly aligned + // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check elimination for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { - Object thatBucket = thatBuckets[i]; - - // nothing in the other hash, just skip this bucket - if (thatBucket == null) continue; + Object thatBucket = thatBuckets[i]; + + // if nothing incoming, nothing to do + if ( thatBucket == null ) continue; Object thisBucket = thisBuckets[i]; - - if (thisBucket == null) { + if (thisBucket == null) { // This bucket is null, easy case // Either copy over the sole entry or clone the BucketGroup chain @@ -475,19 +444,21 @@ public final void putAll(TagMap that) { // there's already an entry w/ the same tag from the incoming TagMap // incoming entry clobbers the existing try, so we're done thisBuckets[i] = thisNewGroup; - this.size += - thisNewGroupSize - - 1; // overlapping group - subtract one for clobbered existing entry + + // overlapping group - subtract one for clobbered existing entry + this.size += thisNewGroupSize - 1; } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) { // able to add thisEntry into the existing groups thisBuckets[i] = thisNewGroup; - this.size += - thisNewGroupSize; // non overlapping group - existing entry already accounted for + + // non overlapping group - existing entry already accounted for in this.size + this.size += thisNewGroupSize; } else { // unable to add into the existing groups thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); - this.size += - thisNewGroupSize; // non overlapping group - existing entry already accounted for + + // non overlapping group - existing entry already accounted for in this.size + this.size += thisNewGroupSize; } } } else if (thisBucket instanceof BucketGroup) { @@ -523,6 +494,32 @@ public final void putAll(TagMap that) { } } } + + /* + * Optimized special case method for the common case of putAll into an empty TagMap used by copy, etc + */ + private final void putAllIntoEmptyMap(TagMap that) { + Object[] thisBuckets = this.buckets; + Object[] thatBuckets = that.buckets; + + // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check elimination + for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { + Object thatBucket = thatBuckets[i]; + + // faster to explicitly null check first, then do instanceof + if ( thatBucket == null ) { + // do nothing + } else if ( thatBucket instanceof BucketGroup ) { + // if it is a BucketGroup, then need to clone + BucketGroup thatGroup = (BucketGroup)thatBucket; + + thisBuckets[i] = thatGroup.cloneChain(); + } else { // if ( thatBucket instanceof Entry ) + thisBuckets[i] = thatBucket; + } + } + this.size = that.size; + } public final void fillMap(Map map) { Object[] thisBuckets = this.buckets; @@ -615,7 +612,7 @@ public final Entry removeEntry(String tag) { /** Returns a mutable copy of this TagMap */ public final TagMap copy() { TagMap copy = new TagMap(); - copy.putAll(this); + copy.putAllIntoEmptyMap(this); return copy; } @@ -745,6 +742,10 @@ public final void checkWriteAccess() { } final void checkIntegrity() { + // Decided to use if ( cond ) throw new IllegalStateException rather than assert + // That was done to avoid the extra static initialization needed for an assertion + // While that's probably an unnecessary optimization, this method is only called in tests + Object[] thisBuckets = this.buckets; for ( int i = 0; i < thisBuckets.length; ++i ) { @@ -789,6 +790,40 @@ final void checkIntegrity() { } } + final int computeSize() { + Object[] thisBuckets = this.buckets; + + int size = 0; + for (int i = 0; i < thisBuckets.length; ++i) { + Object curBucket = thisBuckets[i]; + + if (curBucket instanceof Entry) { + size += 1; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; + size += curGroup.sizeInChain(); + } + } + return size; + } + + final boolean checkIfEmpty() { + Object[] thisBuckets = this.buckets; + + for (int i = 0; i < thisBuckets.length; ++i) { + Object curBucket = thisBuckets[i]; + + if (curBucket instanceof Entry) { + return false; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; + if (!curGroup.isEmptyChain()) return false; + } + } + + return true; + } + @Override public final String toString() { return toPrettyString(); From 0c324b1206e7338196ebeb1a77a18680dc107e5b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 25 Apr 2025 17:28:27 -0400 Subject: [PATCH 75/84] Removing immutable Removed immutable which is a bit dangerous and not needed with both freeze and immutableCopy being available Tweaked special constructor used by createEmpty (and previously by immutable). Now takes a size to be a bit more explicitly about what is happening. --- .../main/java/datadog/trace/api/TagMap.java | 129 +++++++++--------- 1 file changed, 62 insertions(+), 67 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 690d5e35bef..aec37f2ec04 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -34,6 +34,7 @@ * Entries must be replaced by re-setting / re-putting the key which will create a new Entry object. * *

    This map also lacks features designed for handling large long lived mutable maps... + * *

      *
    • bucket array expansion *
    • adaptive collision @@ -67,7 +68,7 @@ private static final TagMap createEmpty() { // Using special constructor that creates a frozen view of an existing array // Bucket calculation requires that array length is a power of 2 // e.g. size 0 will not work, it results in ArrayIndexOutOfBoundsException, but size 1 does - return new TagMap(new Object[1]); + return new TagMap(new Object[1], 0); } /** Creates a new TagMap.Builder */ @@ -108,9 +109,9 @@ public TagMap() { } /** Used for inexpensive immutable */ - private TagMap(Object[] buckets) { + private TagMap(Object[] buckets, int size) { this.buckets = buckets; - this.size = 0; + this.size = size; this.frozen = true; } @@ -359,7 +360,7 @@ public final void putAll(Map map) { this.putAll((TagMap) map); } else { for (Map.Entry entry : map.entrySet()) { - // use set which returns a prior Entry rather put which may box a prior primitive value + // use set which returns a prior Entry rather put which may box a prior primitive value this.set(entry.getKey(), entry.getValue()); } } @@ -373,29 +374,30 @@ public final void putAll(Map map) { * source TagMap */ public final void putAll(TagMap that) { - this.checkWriteAccess(); - - if ( this.size == 0 ) { - this.putAllIntoEmptyMap(that); - } else { - this.putAllMerge(that); - } - } - + this.checkWriteAccess(); + + if (this.size == 0) { + this.putAllIntoEmptyMap(that); + } else { + this.putAllMerge(that); + } + } + private final void putAllMerge(TagMap that) { Object[] thisBuckets = this.buckets; Object[] thatBuckets = that.buckets; // Since TagMap-s don't support expansion, buckets are perfectly aligned - // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check elimination + // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check + // elimination for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { - Object thatBucket = thatBuckets[i]; - - // if nothing incoming, nothing to do - if ( thatBucket == null ) continue; + Object thatBucket = thatBuckets[i]; + + // if nothing incoming, nothing to do + if (thatBucket == null) continue; Object thisBucket = thisBuckets[i]; - if (thisBucket == null) { + if (thisBucket == null) { // This bucket is null, easy case // Either copy over the sole entry or clone the BucketGroup chain @@ -444,19 +446,19 @@ private final void putAllMerge(TagMap that) { // there's already an entry w/ the same tag from the incoming TagMap // incoming entry clobbers the existing try, so we're done thisBuckets[i] = thisNewGroup; - + // overlapping group - subtract one for clobbered existing entry - this.size += thisNewGroupSize - 1; + this.size += thisNewGroupSize - 1; } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) { // able to add thisEntry into the existing groups thisBuckets[i] = thisNewGroup; - + // non overlapping group - existing entry already accounted for in this.size - this.size += thisNewGroupSize; + this.size += thisNewGroupSize; } else { // unable to add into the existing groups thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); - + // non overlapping group - existing entry already accounted for in this.size this.size += thisNewGroupSize; } @@ -494,7 +496,7 @@ private final void putAllMerge(TagMap that) { } } } - + /* * Optimized special case method for the common case of putAll into an empty TagMap used by copy, etc */ @@ -502,20 +504,21 @@ private final void putAllIntoEmptyMap(TagMap that) { Object[] thisBuckets = this.buckets; Object[] thatBuckets = that.buckets; - // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check elimination + // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check + // elimination for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { Object thatBucket = thatBuckets[i]; // faster to explicitly null check first, then do instanceof - if ( thatBucket == null ) { - // do nothing - } else if ( thatBucket instanceof BucketGroup ) { - // if it is a BucketGroup, then need to clone - BucketGroup thatGroup = (BucketGroup)thatBucket; - - thisBuckets[i] = thatGroup.cloneChain(); - } else { // if ( thatBucket instanceof Entry ) - thisBuckets[i] = thatBucket; + if (thatBucket == null) { + // do nothing + } else if (thatBucket instanceof BucketGroup) { + // if it is a BucketGroup, then need to clone + BucketGroup thatGroup = (BucketGroup) thatBucket; + + thisBuckets[i] = thatGroup.cloneChain(); + } else { // if ( thatBucket instanceof Entry ) + thisBuckets[i] = thatBucket; } } this.size = that.size; @@ -628,11 +631,6 @@ public final TagMap immutableCopy() { } } - public final TagMap immutable() { - // specialized constructor, freezes map immediately - return new TagMap(this.buckets); - } - /** * Provides an Iterator over the Entry-s of the TagMap Equivalent to entrySet().iterator() * , but with less allocation @@ -742,50 +740,47 @@ public final void checkWriteAccess() { } final void checkIntegrity() { - // Decided to use if ( cond ) throw new IllegalStateException rather than assert - // That was done to avoid the extra static initialization needed for an assertion - // While that's probably an unnecessary optimization, this method is only called in tests - + // Decided to use if ( cond ) throw new IllegalStateException rather than assert + // That was done to avoid the extra static initialization needed for an assertion + // While that's probably an unnecessary optimization, this method is only called in tests + Object[] thisBuckets = this.buckets; - - for ( int i = 0; i < thisBuckets.length; ++i ) { + + for (int i = 0; i < thisBuckets.length; ++i) { Object thisBucket = thisBuckets[i]; - - if ( thisBucket instanceof Entry ) { - Entry thisEntry = (Entry)thisBucket; + + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; int thisHash = thisEntry.hash(); - + int expectedBucket = thisHash & (thisBuckets.length - 1); - if ( expectedBucket != i ) { + if (expectedBucket != i) { throw new IllegalStateException("incorrect bucket"); } - } else if ( thisBucket instanceof BucketGroup ) { - BucketGroup thisGroup = (BucketGroup)thisBucket; - - for ( BucketGroup curGroup = thisGroup; - curGroup != null; - curGroup = curGroup.prev ) - { - for ( int j = 0; j < BucketGroup.LEN; ++j ) { + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + + for (BucketGroup curGroup = thisGroup; curGroup != null; curGroup = curGroup.prev) { + for (int j = 0; j < BucketGroup.LEN; ++j) { Entry thisEntry = curGroup._entryAt(i); - if ( thisEntry == null ) continue; - + if (thisEntry == null) continue; + int thisHash = thisEntry.hash(); assert curGroup._hashAt(i) == thisHash; - + int expectedBucket = thisHash & (thisBuckets.length - 1); - if ( expectedBucket != i ) { + if (expectedBucket != i) { throw new IllegalStateException("incorrect bucket"); } } } } } - - if ( this.size != this.computeSize() ) { + + if (this.size != this.computeSize()) { throw new IllegalStateException("incorrect size"); } - if ( this.isEmpty() != this.checkIfEmpty() ) { + if (this.isEmpty() != this.checkIfEmpty()) { throw new IllegalStateException("incorrect empty status"); } } @@ -806,7 +801,7 @@ final int computeSize() { } return size; } - + final boolean checkIfEmpty() { Object[] thisBuckets = this.buckets; From 34870521eb9a23cf6009bec855b4edda5b753217 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 25 Apr 2025 17:30:38 -0400 Subject: [PATCH 76/84] spotless & javadoc clean-up --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 2 +- .../src/test/java/datadog/trace/api/TagMapFuzzTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index aec37f2ec04..6eeaf107a78 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -46,7 +46,7 @@ *

      * When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly. *

      - * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain + * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot * directly form a link list to handle collisions. *

      * Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries. diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index 40579a25820..53164ba11ca 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -996,7 +996,7 @@ static final void assertMapEquals(Map expected, TagMap actual) { Object expectedValue = expected.get(actualEntry.tag()); assertEquals(expectedValue, actualEntry.objectValue()); } - + actual.checkIntegrity(); } From f6a5794029dfc3806c5c0efbe1ce27c9d28b7f7b Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Mon, 28 Apr 2025 17:04:44 -0400 Subject: [PATCH 77/84] Tweaking API Introduced EntryChange part class of Entry and EntryRemoval --- .../datadog/trace/core/DDSpanContext.java | 14 +- .../main/java/datadog/trace/api/TagMap.java | 166 +++++++++--------- .../datadog/trace/api/TagMapBuilderTest.java | 4 +- .../datadog/trace/api/TagMapEntryTest.java | 9 +- .../java/datadog/trace/api/TagMapTest.java | 14 ++ 5 files changed, 108 insertions(+), 99 deletions(-) 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 2be6aaf0c93..5feb69a23d5 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 @@ -786,15 +786,17 @@ void setAllTags(final TagMap.Builder builder) { TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { - for (final TagMap.Entry tagEntry : builder) { - if (tagEntry.isRemoval()) { - unsafeTags.removeEntry(tagEntry.tag()); + for (final TagMap.EntryChange entryChange : builder) { + if (entryChange.isRemoval()) { + unsafeTags.removeEntry(entryChange.tag()); } else { - String tag = tagEntry.tag(); - Object value = tagEntry.objectValue(); + TagMap.Entry entry = (TagMap.Entry) entryChange; + + String tag = entry.tag(); + Object value = entry.objectValue(); if (!tagInterceptor.interceptTag(this, tag, value)) { - unsafeTags.putEntry(tagEntry); + unsafeTags.putEntry(entry); } } } diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 6eeaf107a78..a8b0be00cf1 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -47,7 +47,7 @@ * When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly. *

      * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot - * directly form a link list to handle collisions. + * directly form a linked list to handle collisions. *

      * Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries. * But a BucketGroup is only formed when a collision occurs to keep allocation low in the common case of no collisions. @@ -88,7 +88,7 @@ public static final TagMap fromMap(Map map) { return tagMap; } - /** Creates a new immutable TagMap that contains the contents map */ + /** Creates a new immutable TagMap that contains the contents of map */ public static final TagMap fromMapImmutable(Map map) { if (map.isEmpty()) { return TagMap.EMPTY; @@ -338,17 +338,17 @@ public final void putAll(Iterable entries) { } public final void putAll(TagMap.Builder builder) { - putAll(builder.entries, builder.nextPos); + putAll(builder.entryChanges, builder.nextPos); } - private final void putAll(Entry[] tagEntries, int size) { - for (int i = 0; i < size && i < tagEntries.length; ++i) { - Entry tagEntry = tagEntries[i]; + private final void putAll(EntryChange[] entryChanges, int size) { + for (int i = 0; i < size && i < entryChanges.length; ++i) { + EntryChange change = entryChanges[i]; - if (tagEntry.isRemoval()) { - this.remove(tagEntry.tag); + if (change.isRemoval()) { + this.remove(change.tag()); } else { - this.putEntry(tagEntry); + this.putEntry((Entry) change); } } } @@ -367,9 +367,9 @@ public final void putAll(Map map) { } /** - * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another + * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from one TagMap to another * - *

      This method takes advantage of the consistent Map layout to optimize the handling of each + *

      This method takes advantage of the consistent TagMap layout to optimize the handling of each * bucket. And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the * source TagMap */ @@ -498,7 +498,7 @@ private final void putAllMerge(TagMap that) { } /* - * Optimized special case method for the common case of putAll into an empty TagMap used by copy, etc + * Specially optimized version of putAll for the common case of destination map being empty */ private final void putAllIntoEmptyMap(TagMap that) { Object[] thisBuckets = this.buckets; @@ -878,13 +878,40 @@ static final int _hash(String tag) { return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); } - public static final class Entry implements Map.Entry { - /* - * Special value used to record removals in the builder - * Removal entries are never stored in the TagMap itself - */ - private static final byte REMOVED = -1; + public abstract static class EntryChange { + public static final EntryRemoval newRemoval(String tag) { + return new EntryRemoval(tag); + } + + final String tag; + + EntryChange(String tag) { + this.tag = tag; + } + + public final String tag() { + return this.tag; + } + + public final boolean matches(String tag) { + return this.tag.equals(tag); + } + + public abstract boolean isRemoval(); + } + + public static final class EntryRemoval extends EntryChange { + EntryRemoval(String tag) { + super(tag); + } + + @Override + public final boolean isRemoval() { + return true; + } + } + public static final class Entry extends EntryChange implements Map.Entry { /* * Special value used for Objects that haven't been type checked yet. * These objects might be primitive box objects. @@ -963,17 +990,11 @@ static final Entry newDoubleEntry(String tag, Double box) { return new Entry(tag, DOUBLE, double2Prim(box.doubleValue()), box); } - static final Entry newRemovalEntry(String tag) { - return new Entry(tag, REMOVED, 0, null); - } - - final String tag; - /* * hash is stored in line for fast handling of Entry-s coming another Tag * However, hash is lazily computed using the same trick as {@link java.lang.String}. */ - int lazyHash; + int lazyTagHash; // To optimize construction of Entry around boxed primitives and Object entries, // no type checks are done during construction. @@ -996,26 +1017,22 @@ static final Entry newRemovalEntry(String tag) { volatile String strCache = null; private Entry(String tag, byte type, long prim, Object obj) { - this.tag = tag; - this.lazyHash = 0; // lazily computed + super(tag); + this.lazyTagHash = 0; // lazily computed this.rawType = type; this.rawPrim = prim; this.rawObj = obj; } - public final String tag() { - return this.tag; - } - int hash() { // If value of hash read in this thread is zero, then hash is computed. // hash is not held as a volatile, since this computation can safely be repeated as any time - int hash = this.lazyHash; + int hash = this.lazyTagHash; if (hash != 0) return hash; hash = _hash(this.tag); - this.lazyHash = hash; + this.lazyTagHash = hash; return hash; } @@ -1105,11 +1122,7 @@ public final boolean isObject() { } public final boolean isRemoval() { - return this.is(REMOVED); - } - - public final boolean matches(String tag) { - return this.tag.equals(tag); + return false; } public final Object objectValue() { @@ -1146,10 +1159,6 @@ public final Object objectValue() { break; } - if (this.is(REMOVED)) { - return null; - } - return this.rawObj; } @@ -1183,9 +1192,6 @@ public final boolean booleanValue() { case OBJECT: return (this.rawObj != null); - - case REMOVED: - return false; } return false; @@ -1221,9 +1227,6 @@ public final int intValue() { case OBJECT: return 0; - - case REMOVED: - return 0; } return 0; @@ -1259,9 +1262,6 @@ public final long longValue() { case OBJECT: return 0; - - case REMOVED: - return 0; } return 0; @@ -1297,9 +1297,6 @@ public final float floatValue() { case OBJECT: return 0F; - - case REMOVED: - return 0F; } return 0F; @@ -1335,9 +1332,6 @@ public final double doubleValue() { case OBJECT: return 0D; - - case REMOVED: - return 0D; } return 0D; @@ -1373,9 +1367,6 @@ private final String computeStringValue() { case DOUBLE: return Double.toString(prim2Double(this.rawPrim)); - case REMOVED: - return null; - case OBJECT: case ANY: return this.rawObj.toString(); @@ -1448,8 +1439,8 @@ private static final double prim2Double(long prim) { } } - public static final class Builder implements Iterable { - private Entry[] entries; + public static final class Builder implements Iterable { + private EntryChange[] entryChanges; private int nextPos = 0; private boolean containsRemovals = false; @@ -1458,7 +1449,7 @@ private Builder() { } private Builder(int size) { - this.entries = new Entry[size]; + this.entryChanges = new EntryChange[size]; } public final boolean isDefinitelyEmpty() { @@ -1518,7 +1509,7 @@ public final Builder put(Entry entry) { } public final Builder remove(String tag) { - return this.recordRemoval(Entry.newRemovalEntry(tag)); + return this.recordRemoval(EntryChange.newRemoval(tag)); } private final Builder recordEntry(Entry entry) { @@ -1526,19 +1517,19 @@ private final Builder recordEntry(Entry entry) { return this; } - private final Builder recordRemoval(Entry entry) { + private final Builder recordRemoval(EntryRemoval entry) { this.recordChange(entry); this.containsRemovals = true; return this; } - private final void recordChange(Entry entry) { - if (this.nextPos >= this.entries.length) { - this.entries = Arrays.copyOf(this.entries, this.entries.length << 1); + private final void recordChange(EntryChange entryChange) { + if (this.nextPos >= this.entryChanges.length) { + this.entryChanges = Arrays.copyOf(this.entryChanges, this.entryChanges.length << 1); } - this.entries[this.nextPos++] = entry; + this.entryChanges[this.nextPos++] = entryChange; } public final Builder smartRemove(String tag) { @@ -1549,11 +1540,12 @@ public final Builder smartRemove(String tag) { } private final boolean contains(String tag) { - Entry[] thisEntries = this.entries; + EntryChange[] thisChanges = this.entryChanges; - // min is to clamp, so ArrayBoundsCheckElimination optimization works - for (int i = 0; i < Math.min(this.nextPos, thisEntries.length); ++i) { - if (thisEntries[i].matches(tag)) return true; + // min is to clamp, so bounds check elimination optimization works + int lenClamp = Math.min(this.nextPos, thisChanges.length); + for (int i = 0; i < lenClamp; ++i) { + if (thisChanges[i].matches(tag)) return true; } return false; } @@ -1562,28 +1554,30 @@ private final boolean contains(String tag) { * Just for testing */ final Entry findLastEntry(String tag) { - Entry[] thisEntries = this.entries; + EntryChange[] thisChanges = this.entryChanges; // min is to clamp, so ArrayBoundsCheckElimination optimization works - for (int i = Math.min(this.nextPos, thisEntries.length) - 1; i >= 0; --i) { - if (thisEntries[i].matches(tag)) return thisEntries[i]; + int clampLen = Math.min(this.nextPos, thisChanges.length) - 1; + for (int i = clampLen; i >= 0; --i) { + EntryChange thisChange = thisChanges[i]; + if (!thisChange.isRemoval() && thisChange.matches(tag)) return (Entry) thisChange; } return null; } public final void reset() { - Arrays.fill(this.entries, null); + Arrays.fill(this.entryChanges, null); this.nextPos = 0; } @Override - public final Iterator iterator() { - return new BuilderIterator(this.entries, this.nextPos); + public final Iterator iterator() { + return new BuilderIterator(this.entryChanges, this.nextPos); } public TagMap build() { TagMap map = new TagMap(); - if (this.nextPos != 0) map.putAll(this.entries, this.nextPos); + if (this.nextPos != 0) map.putAll(this.entryChanges, this.nextPos); return map; } @@ -1596,14 +1590,14 @@ public TagMap buildImmutable() { } } - private static final class BuilderIterator implements Iterator { - private final Entry[] entries; + private static final class BuilderIterator implements Iterator { + private final EntryChange[] entryChanges; private final int size; private int pos; - BuilderIterator(Entry[] entries, int size) { - this.entries = entries; + BuilderIterator(EntryChange[] entryChanges, int size) { + this.entryChanges = entryChanges; this.size = size; this.pos = -1; @@ -1615,10 +1609,10 @@ public final boolean hasNext() { } @Override - public Entry next() { + public EntryChange next() { if (!this.hasNext()) throw new NoSuchElementException("no next"); - return this.entries[++this.pos]; + return this.entryChanges[++this.pos]; } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java index e8067c96b5b..e71d4b57abc 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java @@ -23,7 +23,9 @@ public void inOrder() { assertEquals(SIZE, builder.estimateSize()); int i = 0; - for (TagMap.Entry entry : builder) { + for (TagMap.EntryChange entryChange : builder) { + TagMap.Entry entry = (TagMap.Entry) entryChange; + assertEquals(key(i), entry.tag()); assertEquals(value(i), entry.stringValue()); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index d0be8fd0172..95f429737aa 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -1,6 +1,5 @@ package datadog.trace.api; -import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -323,11 +322,9 @@ public void anyEntry_double(double value) { } @Test - public void removalEntry() { - TagMap.Entry removalEntry = TagMap.Entry.newRemovalEntry("foo"); - assertTrue(removalEntry.isRemoval()); - - assertNull(removalEntry.objectValue()); + public void removalChange() { + TagMap.EntryChange removalChange = TagMap.EntryChange.newRemoval("foo"); + assertTrue(removalChange.isRemoval()); } static final int NUM_THREADS = 4; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index bc51605ba36..4ac773ed045 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -218,6 +218,20 @@ public void copyMany() { assertSize(size, copy); } + @Test + public void immutableCopy() { + int size = randomSize(); + TagMap orig = createTagMap(size); + + TagMap immutableCopy = orig.immutableCopy(); + orig.clear(); // doing this to make sure that copied isn't modified + + for (int i = 0; i < size; ++i) { + assertEntry(key(i), value(i), immutableCopy); + } + assertSize(size, immutableCopy); + } + @Test public void replaceALot() { int size = randomSize(); From a395a5de50d2d9f2c661975a7eb3225f92ce306e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 29 Apr 2025 10:41:29 -0400 Subject: [PATCH 78/84] Changing TagMap.Builder -> TagMap.Ledger Renamed TagMap.Builder -> TagMap.Ledger to make the concept a bit more clear Also renamed Builder.put methods to Ledger.set to be more consistent with TagMap Update users of TagMap.Builder as needed --- .../java/datadog/trace/core/CoreTracer.java | 14 +- .../datadog/trace/core/DDSpanContext.java | 6 +- .../trace/core/propagation/B3HttpCodec.java | 4 +- .../core/propagation/ContextInterpreter.java | 22 +-- .../main/java/datadog/trace/api/TagMap.java | 98 +++++++------ ...BuilderTest.java => TagMapLedgerTest.java} | 132 +++++++++--------- 6 files changed, 137 insertions(+), 139 deletions(-) rename internal-api/src/test/java/datadog/trace/api/{TagMapBuilderTest.java => TagMapLedgerTest.java} (51%) 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 cae1c98d28c..44ad2d4e81a 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 @@ -1380,7 +1380,7 @@ public static class CoreSpanBuilder implements AgentTracer.SpanBuilder { private final CoreTracer tracer; // Builder attributes - private TagMap.Builder tagBuilder; + private TagMap.Ledger tagLedger; private long timestampMicro; private AgentSpanContext parent; private String serviceName; @@ -1513,11 +1513,11 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { if (tag == null) { return this; } - TagMap.Builder tagBuilder = this.tagBuilder; - if (tagBuilder == null) { + TagMap.Ledger tagLedger = this.tagLedger; + if (tagLedger == null) { // Insertion order is important, so using TagBuilder which builds up a set // of Entry modifications in order - this.tagBuilder = tagBuilder = TagMap.builder(); + this.tagLedger = tagLedger = TagMap.ledger(); } if (value == null) { // DQH - Use of smartRemove is important to avoid clobbering entries added by another map @@ -1525,9 +1525,9 @@ public CoreSpanBuilder withTag(final String tag, final Object value) { // builder // smartRemove is O(n) but since removes are rare, this is preferable to a more complicated // implementation in setAll - tagBuilder.smartRemove(tag); + tagLedger.smartRemove(tag); } else { - tagBuilder.put(tag, value); + tagLedger.set(tag, value); } return this; } @@ -1815,7 +1815,7 @@ private DDSpanContext buildSpanContext() { // the builder. This is the order that the tags were added previously, but maybe the `tags` // set in the builder should come last, so that they override other tags. context.setAllTags(mergedTracerTags, mergedTracerTagsNeedsIntercept); - context.setAllTags(tagBuilder); + context.setAllTags(tagLedger); context.setAllTags(coreTags, coreTagsNeedsIntercept); context.setAllTags(rootSpanTags, rootSpanTagsNeedsIntercept); context.setAllTags(contextualTags); 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 5feb69a23d5..3e522d94f72 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 @@ -779,14 +779,14 @@ void setAllTags(final TagMap map, boolean needsIntercept) { } } - void setAllTags(final TagMap.Builder builder) { - if (builder == null) { + void setAllTags(final TagMap.Ledger ledger) { + if (ledger == null) { return; } TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { - for (final TagMap.EntryChange entryChange : builder) { + for (final TagMap.EntryChange entryChange : ledger) { if (entryChange.isRemoval()) { unsafeTags.removeEntry(entryChange.tag()); } else { diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java index c9af7dec2a2..ef807f6b080 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/B3HttpCodec.java @@ -185,7 +185,7 @@ public B3BaseContextInterpreter(Config config) { protected void setSpanId(final String sId) { spanId = DDSpanId.fromHex(sId); - tagBuilder().put(B3_SPAN_ID, sId); + tagLedger().set(B3_SPAN_ID, sId); } protected boolean setTraceId(final String tId) { @@ -198,7 +198,7 @@ protected boolean setTraceId(final String tId) { B3TraceId b3TraceId = B3TraceId.fromHex(tId); traceId = b3TraceId.toLong() == 0 ? DDTraceId.ZERO : b3TraceId; } - tagBuilder().put(B3_TRACE_ID, tId); + tagLedger().set(B3_TRACE_ID, tId); return true; } } diff --git a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java index 972c4b6d43e..e94097db2a4 100644 --- a/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java +++ b/dd-trace-core/src/main/java/datadog/trace/core/propagation/ContextInterpreter.java @@ -45,7 +45,7 @@ public abstract class ContextInterpreter implements AgentPropagation.KeyClassifi protected DDTraceId traceId; protected long spanId; protected int samplingPriority; - protected TagMap.Builder tagBuilder; + protected TagMap.Ledger tagLedger; protected Map baggage; protected CharSequence lastParentId; @@ -78,11 +78,11 @@ protected ContextInterpreter(Config config) { this.requestHeaderTagsCommaAllowed = config.isRequestHeaderTagsCommaAllowed(); } - final TagMap.Builder tagBuilder() { - if (tagBuilder == null) { - tagBuilder = TagMap.builder(); + final TagMap.Ledger tagLedger() { + if (tagLedger == null) { + tagLedger = TagMap.ledger(); } - return tagBuilder; + return tagLedger; } /** @@ -197,8 +197,8 @@ protected final boolean handleTags(String key, String value) { final String lowerCaseKey = toLowerCase(key); final String mappedKey = headerTags.get(lowerCaseKey); if (null != mappedKey) { - tagBuilder() - .put( + tagLedger() + .set( mappedKey, HttpCodec.decode( requestHeaderTagsCommaAllowed ? value : HttpCodec.firstHeaderValue(value))); @@ -230,7 +230,7 @@ public ContextInterpreter reset(TraceConfig traceConfig) { samplingPriority = PrioritySampling.UNSET; origin = null; endToEndStartTime = 0; - if (tagBuilder != null) tagBuilder.reset(); + if (tagLedger != null) tagLedger.reset(); baggage = Collections.emptyMap(); valid = true; fullContext = true; @@ -258,19 +258,19 @@ protected TagContext build() { origin, endToEndStartTime, baggage, - tagBuilder == null ? null : tagBuilder.build(), + tagLedger == null ? null : tagLedger.build(), httpHeaders, propagationTags, traceConfig, style()); } else if (origin != null - || (tagBuilder != null && !tagBuilder.isDefinitelyEmpty()) + || (tagLedger != null && !tagLedger.isDefinitelyEmpty()) || httpHeaders != null || !baggage.isEmpty() || samplingPriority != PrioritySampling.UNSET) { return new TagContext( origin, - tagBuilder == null ? null : tagBuilder.build(), + tagLedger == null ? null : tagLedger.build(), httpHeaders, baggage, samplingPriorityOrDefault(traceId, samplingPriority), diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index a8b0be00cf1..6f4b709816b 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -20,7 +20,7 @@ * *

        *
      • fast copy from one map to another - *
      • compatibility with Builder idioms + *
      • compatibility with builder idioms *
      • building small maps as fast as possible *
      • storing primitives without boxing *
      • minimal memory footprint @@ -71,14 +71,14 @@ private static final TagMap createEmpty() { return new TagMap(new Object[1], 0); } - /** Creates a new TagMap.Builder */ - public static final Builder builder() { - return new Builder(); + /** Creates a new TagMap.Ledger */ + public static final Ledger ledger() { + return new Ledger(); } - /** Creates a new TagMap.Builder which handles size modifications before expansion */ - public static final Builder builder(int size) { - return new Builder(size); + /** Creates a new TagMap.Ledger which handles size modifications before expansion */ + public static final Ledger ledger(int size) { + return new Ledger(size); } /** Creates a new mutable TagMap that contains the contents of map */ @@ -337,8 +337,8 @@ public final void putAll(Iterable entries) { } } - public final void putAll(TagMap.Builder builder) { - putAll(builder.entryChanges, builder.nextPos); + public final void putAll(TagMap.Ledger ledger) { + putAll(ledger.entryChanges, ledger.nextPos); } private final void putAll(EntryChange[] entryChanges, int size) { @@ -830,19 +830,19 @@ public final String toString() { final String toPrettyString() { boolean first = true; - StringBuilder builder = new StringBuilder(128); - builder.append('{'); + StringBuilder ledger = new StringBuilder(128); + ledger.append('{'); for (Entry entry : this) { if (first) { first = false; } else { - builder.append(", "); + ledger.append(", "); } - builder.append(entry.tag).append('=').append(entry.stringValue()); + ledger.append(entry.tag).append('=').append(entry.stringValue()); } - builder.append('}'); - return builder.toString(); + ledger.append('}'); + return ledger.toString(); } /** @@ -852,25 +852,25 @@ final String toPrettyString() { final String toInternalString() { Object[] thisBuckets = this.buckets; - StringBuilder builder = new StringBuilder(128); + StringBuilder ledger = new StringBuilder(128); for (int i = 0; i < thisBuckets.length; ++i) { - builder.append('[').append(i).append("] = "); + ledger.append('[').append(i).append("] = "); Object thisBucket = thisBuckets[i]; if (thisBucket == null) { - builder.append("null"); + ledger.append("null"); } else if (thisBucket instanceof Entry) { - builder.append('{').append(thisBucket).append('}'); + ledger.append('{').append(thisBucket).append('}'); } else if (thisBucket instanceof BucketGroup) { for (BucketGroup curGroup = (BucketGroup) thisBucket; curGroup != null; curGroup = curGroup.prev) { - builder.append(curGroup).append(" -> "); + ledger.append(curGroup).append(" -> "); } } - builder.append('\n'); + ledger.append('\n'); } - return builder.toString(); + return ledger.toString(); } static final int _hash(String tag) { @@ -1439,16 +1439,20 @@ private static final double prim2Double(long prim) { } } - public static final class Builder implements Iterable { + /* + * An in-order ledger of changes to be made to a TagMap. + * Ledger can also serves as a builder for TagMap-s via build & buildImmutable. + */ + public static final class Ledger implements Iterable { private EntryChange[] entryChanges; private int nextPos = 0; private boolean containsRemovals = false; - private Builder() { + private Ledger() { this(8); } - private Builder(int size) { + private Ledger(int size) { this.entryChanges = new EntryChange[size]; } @@ -1457,7 +1461,7 @@ public final boolean isDefinitelyEmpty() { } /** - * Provides the estimated size of the map created by the builder Doesn't account for overwritten + * Provides the estimated size of the map created by the ledger Doesn't account for overwritten * entries or entry removal * * @return @@ -1470,54 +1474,48 @@ public final boolean containsRemovals() { return this.containsRemovals; } - public final Builder put(String tag, Object value) { + public final Ledger set(String tag, Object value) { return this.recordEntry(Entry.newAnyEntry(tag, value)); } - public final Builder put(String tag, CharSequence value) { + public final Ledger set(String tag, CharSequence value) { return this.recordEntry(Entry.newObjectEntry(tag, value)); } - public final Builder put(String tag, boolean value) { + public final Ledger set(String tag, boolean value) { return this.recordEntry(Entry.newBooleanEntry(tag, value)); } - public final Builder put(String tag, int value) { + public final Ledger set(String tag, int value) { return this.recordEntry(Entry.newIntEntry(tag, value)); } - public final Builder put(String tag, long value) { + public final Ledger set(String tag, long value) { return this.recordEntry(Entry.newLongEntry(tag, value)); } - public final Builder put(String tag, float value) { + public final Ledger set(String tag, float value) { return this.recordEntry(Entry.newFloatEntry(tag, value)); } - public final Builder put(String tag, double value) { + public final Ledger set(String tag, double value) { return this.recordEntry(Entry.newDoubleEntry(tag, value)); } - public final Builder uncheckedPut(Entry entry) { + public final Ledger set(Entry entry) { return this.recordEntry(entry); } - public final Builder put(Entry entry) { - this.recordChange(entry); - this.containsRemovals |= entry.isRemoval(); - return this; - } - - public final Builder remove(String tag) { + public final Ledger remove(String tag) { return this.recordRemoval(EntryChange.newRemoval(tag)); } - private final Builder recordEntry(Entry entry) { + private final Ledger recordEntry(Entry entry) { this.recordChange(entry); return this; } - private final Builder recordRemoval(EntryRemoval entry) { + private final Ledger recordRemoval(EntryRemoval entry) { this.recordChange(entry); this.containsRemovals = true; @@ -1532,7 +1530,7 @@ private final void recordChange(EntryChange entryChange) { this.entryChanges[this.nextPos++] = entryChange; } - public final Builder smartRemove(String tag) { + public final Ledger smartRemove(String tag) { if (this.contains(tag)) { this.remove(tag); } @@ -2153,15 +2151,15 @@ BucketGroup _cloneEntries() { @Override public String toString() { - StringBuilder builder = new StringBuilder(32); - builder.append('['); + StringBuilder ledger = new StringBuilder(32); + ledger.append('['); for (int i = 0; i < BucketGroup.LEN; ++i) { - if (i != 0) builder.append(", "); + if (i != 0) ledger.append(", "); - builder.append(this._entryAt(i)); + ledger.append(this._entryAt(i)); } - builder.append(']'); - return builder.toString(); + ledger.append(']'); + return ledger.toString(); } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java similarity index 51% rename from internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java rename to internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java index e71d4b57abc..6c4e49cb6bb 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBuilderTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java @@ -10,20 +10,20 @@ import org.junit.jupiter.api.Test; -public class TagMapBuilderTest { +public class TagMapLedgerTest { static final int SIZE = 32; @Test public void inOrder() { - TagMap.Builder builder = TagMap.builder(); + TagMap.Ledger ledger = TagMap.ledger(); for (int i = 0; i < SIZE; ++i) { - builder.put(key(i), value(i)); + ledger.set(key(i), value(i)); } - assertEquals(SIZE, builder.estimateSize()); + assertEquals(SIZE, ledger.estimateSize()); int i = 0; - for (TagMap.EntryChange entryChange : builder) { + for (TagMap.EntryChange entryChange : ledger) { TagMap.Entry entry = (TagMap.Entry) entryChange; assertEquals(key(i), entry.tag()); @@ -35,34 +35,34 @@ public void inOrder() { @Test public void testTypes() { - TagMap.Builder builder = TagMap.builder(); - builder.put("bool", true); - builder.put("int", 1); - builder.put("long", 1L); - builder.put("float", 1F); - builder.put("double", 1D); - builder.put("object", (Object) "string"); - builder.put("string", "string"); - - assertEntryRawType(TagMap.Entry.BOOLEAN, builder, "bool"); - assertEntryRawType(TagMap.Entry.INT, builder, "int"); - assertEntryRawType(TagMap.Entry.LONG, builder, "long"); - assertEntryRawType(TagMap.Entry.FLOAT, builder, "float"); - assertEntryRawType(TagMap.Entry.DOUBLE, builder, "double"); - assertEntryRawType(TagMap.Entry.ANY, builder, "object"); - assertEntryRawType(TagMap.Entry.OBJECT, builder, "string"); + TagMap.Ledger ledger = TagMap.ledger(); + ledger.set("bool", true); + ledger.set("int", 1); + ledger.set("long", 1L); + ledger.set("float", 1F); + ledger.set("double", 1D); + ledger.set("object", (Object) "string"); + ledger.set("string", "string"); + + assertEntryRawType(TagMap.Entry.BOOLEAN, ledger, "bool"); + assertEntryRawType(TagMap.Entry.INT, ledger, "int"); + assertEntryRawType(TagMap.Entry.LONG, ledger, "long"); + assertEntryRawType(TagMap.Entry.FLOAT, ledger, "float"); + assertEntryRawType(TagMap.Entry.DOUBLE, ledger, "double"); + assertEntryRawType(TagMap.Entry.ANY, ledger, "object"); + assertEntryRawType(TagMap.Entry.OBJECT, ledger, "string"); } @Test public void buildMutable() { - TagMap.Builder builder = TagMap.builder(); + TagMap.Ledger ledger = TagMap.ledger(); for (int i = 0; i < SIZE; ++i) { - builder.put(key(i), value(i)); + ledger.set(key(i), value(i)); } - assertEquals(SIZE, builder.estimateSize()); + assertEquals(SIZE, ledger.estimateSize()); - TagMap map = builder.build(); + TagMap map = ledger.build(); for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } @@ -74,14 +74,14 @@ public void buildMutable() { @Test public void buildImmutable() { - TagMap.Builder builder = TagMap.builder(); + TagMap.Ledger ledger = TagMap.ledger(); for (int i = 0; i < SIZE; ++i) { - builder.put(key(i), value(i)); + ledger.set(key(i), value(i)); } - assertEquals(SIZE, builder.estimateSize()); + assertEquals(SIZE, ledger.estimateSize()); - TagMap map = builder.buildImmutable(); + TagMap map = ledger.buildImmutable(); for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } @@ -92,37 +92,37 @@ public void buildImmutable() { @Test public void build_empty() { - TagMap.Builder builder = TagMap.builder(); - assertTrue(builder.isDefinitelyEmpty()); - assertNotSame(TagMap.EMPTY, builder.build()); + TagMap.Ledger ledger = TagMap.ledger(); + assertTrue(ledger.isDefinitelyEmpty()); + assertNotSame(TagMap.EMPTY, ledger.build()); } @Test public void buildImmutable_empty() { - TagMap.Builder builder = TagMap.builder(); - assertTrue(builder.isDefinitelyEmpty()); - assertSame(TagMap.EMPTY, builder.buildImmutable()); + TagMap.Ledger ledger = TagMap.ledger(); + assertTrue(ledger.isDefinitelyEmpty()); + assertSame(TagMap.EMPTY, ledger.buildImmutable()); } @Test public void isDefinitelyEmpty_emptyMap() { - TagMap.Builder builder = TagMap.builder(); - builder.put("foo", "bar"); - builder.remove("foo"); + TagMap.Ledger ledger = TagMap.ledger(); + ledger.set("foo", "bar"); + ledger.remove("foo"); - assertFalse(builder.isDefinitelyEmpty()); - TagMap map = builder.build(); + assertFalse(ledger.isDefinitelyEmpty()); + TagMap map = ledger.build(); assertTrue(map.checkIfEmpty()); } @Test public void builderExpansion() { - TagMap.Builder builder = TagMap.builder(); + TagMap.Ledger ledger = TagMap.ledger(); for (int i = 0; i < 100; ++i) { - builder.put(key(i), value(i)); + ledger.set(key(i), value(i)); } - TagMap map = builder.build(); + TagMap map = ledger.build(); for (int i = 0; i < 100; ++i) { assertEquals(value(i), map.getString(key(i))); } @@ -130,12 +130,12 @@ public void builderExpansion() { @Test public void builderPresized() { - TagMap.Builder builder = TagMap.builder(100); + TagMap.Ledger ledger = TagMap.ledger(100); for (int i = 0; i < 100; ++i) { - builder.put(key(i), value(i)); + ledger.set(key(i), value(i)); } - TagMap map = builder.build(); + TagMap map = ledger.build(); for (int i = 0; i < 100; ++i) { assertEquals(value(i), map.getString(key(i))); } @@ -143,16 +143,16 @@ public void builderPresized() { @Test public void buildWithRemoves() { - TagMap.Builder builder = TagMap.builder(); + TagMap.Ledger ledger = TagMap.ledger(); for (int i = 0; i < SIZE; ++i) { - builder.put(key(i), value(i)); + ledger.set(key(i), value(i)); } for (int i = 0; i < SIZE; i += 2) { - builder.remove(key(i)); + ledger.remove(key(i)); } - TagMap map = builder.build(); + TagMap map = ledger.build(); for (int i = 0; i < SIZE; ++i) { if ((i % 2) == 0) { assertNull(map.getString(key(i))); @@ -164,32 +164,32 @@ public void buildWithRemoves() { @Test public void smartRemoval_existingCase() { - TagMap.Builder builder = TagMap.builder(); - builder.put("foo", "bar"); - builder.smartRemove("foo"); + TagMap.Ledger ledger = TagMap.ledger(); + ledger.set("foo", "bar"); + ledger.smartRemove("foo"); - assertTrue(builder.containsRemovals()); + assertTrue(ledger.containsRemovals()); } @Test public void smartRemoval_missingCase() { - TagMap.Builder builder = TagMap.builder(); - builder.smartRemove("foo"); + TagMap.Ledger ledger = TagMap.ledger(); + ledger.smartRemove("foo"); - assertFalse(builder.containsRemovals()); + assertFalse(ledger.containsRemovals()); } @Test public void reset() { - TagMap.Builder builder = TagMap.builder(2); + TagMap.Ledger ledger = TagMap.ledger(2); - builder.put(key(0), value(0)); - TagMap map0 = builder.build(); + ledger.set(key(0), value(0)); + TagMap map0 = ledger.build(); - builder.reset(); + ledger.reset(); - builder.put(key(1), value(1)); - TagMap map1 = builder.build(); + ledger.set(key(1), value(1)); + TagMap map1 = ledger.build(); assertEquals(value(0), map0.getString(key(0))); assertNull(map1.getString(key(0))); @@ -206,15 +206,15 @@ static final String value(int i) { return "value-" + i; } - static final void assertEntryRawType(byte expectedType, TagMap.Builder builder, String tag) { - TagMap.Entry entry = builder.findLastEntry(tag); + static final void assertEntryRawType(byte expectedType, TagMap.Ledger ledger, String tag) { + TagMap.Entry entry = ledger.findLastEntry(tag); assertEquals(expectedType, entry.rawType); } static final void assertFrozen(TagMap map) { IllegalStateException ex = null; try { - map.put("foo", "bar"); + map.set("foo", "bar"); } catch (IllegalStateException e) { ex = e; } From 9f610f96f9c8a93d9b99195087b620df58ac021e Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 8 May 2025 16:08:50 -0400 Subject: [PATCH 79/84] TagMap that can "mostly" be toggled on / off Changes TagMap to be an interface with two distinct implementations: - One that extends HashMap - One that implements the new map structure Next change will make it possible to switch between the map implementations via configuration To try and make both TagMap implementations reasonably efficient, added a more method variations: - set methods now return void to avoid Entry creation for HashMap implementation - getAndSet - replaces set for the add cases where the previous Entry is needed - remove(String) - now returns a boolean to indicate that a change happened rather than returning the value or entry - getAndRemove - returns an Entry Many TagMapTest and TagMapLedgerTest have been parameterized to test both implementations of TagMaps Updated calling code & other tests as needed --- .../decorator/HttpServerDecorator.java | 3 +- .../DefaultExceptionDebuggerTest.java | 2 +- .../gateway/GatewayBridgeSpecification.groovy | 2 +- .../java/datadog/trace/core/CoreTracer.java | 2 +- .../datadog/trace/core/DDSpanContext.java | 11 +- .../core/tagprocessor/IntegrationAdder.java | 12 +- .../tagprocessor/PayloadTagsProcessor.java | 2 +- .../DefaultPathwayContextTest.groovy | 5 +- .../main/java/datadog/trace/api/Config.java | 4 +- .../main/java/datadog/trace/api/TagMap.java | 3050 ++++++++++------- .../instrumentation/api/TagContext.java | 2 +- .../trace/api/TagMapBucketGroupTest.java | 122 +- .../datadog/trace/api/TagMapEntryTest.java | 113 +- .../datadog/trace/api/TagMapFuzzTest.java | 27 +- .../datadog/trace/api/TagMapLedgerTest.java | 55 +- .../java/datadog/trace/api/TagMapTest.java | 327 +- .../java/datadog/trace/api/TagMapType.java | 20 + .../datadog/trace/api/TagMapTypePair.java | 16 + 18 files changed, 2259 insertions(+), 1516 deletions(-) create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapType.java create mode 100644 internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java index 41330ffbe4c..32289160c75 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/HttpServerDecorator.java @@ -13,6 +13,7 @@ import datadog.trace.api.Config; import datadog.trace.api.DDTags; import datadog.trace.api.DDTraceId; +import datadog.trace.api.TagMap; import datadog.trace.api.TraceConfig; import datadog.trace.api.function.TriConsumer; import datadog.trace.api.function.TriFunction; @@ -408,7 +409,7 @@ public String getSpanType() { } @Override - public Map getTags() { + public TagMap getTags() { return serverSpan.getTags(); } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java index a00d72095db..721409ad94f 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/exception/DefaultExceptionDebuggerTest.java @@ -57,7 +57,7 @@ public class DefaultExceptionDebuggerTest { private ConfigurationUpdater configurationUpdater; private DefaultExceptionDebugger exceptionDebugger; private TestSnapshotListener listener; - private TagMap spanTags = new TagMap(); + private TagMap spanTags = TagMap.create(); @BeforeEach public void setUp() { diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy index 0d229842cd3..503ae7edd8c 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy @@ -989,7 +989,7 @@ class GatewayBridgeSpecification extends DDSpecification { getTraceSegment() >> traceSegment } final spanInfo = Mock(AgentSpan) { - getTags() >> ['http.route':'/'] + getTags() >> TagMap.fromMap(['http.route':'/']) } when: 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 7760eb75abc..c3689de1f33 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 @@ -1888,7 +1888,7 @@ protected ConfigSnapshot( */ static TagMap withTracerTags( Map userSpanTags, Config config, TraceConfig traceConfig) { - final TagMap result = new TagMap(); + final TagMap result = TagMap.create(); result.putAll(userSpanTags); if (null != config) { // static if (!config.getEnv().isEmpty()) { 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 09bf89e6600..c6eaa0518e8 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 @@ -342,7 +342,10 @@ public DDSpanContext( assert pathwayContext != null; this.pathwayContext = pathwayContext; - this.unsafeTags = new TagMap(); + // 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); // must set this before setting the service and resource names below this.profilingContextIntegration = profilingContextIntegration; @@ -772,7 +775,7 @@ void setAllTags(final TagMap map, boolean needsIntercept) { Object value = tagEntry.objectValue(); if (!tagInterceptor.interceptTag(ctx, tag, value)) { - ctx.unsafeTags.putEntry(tagEntry); + ctx.unsafeTags.set(tagEntry); } }); } else { @@ -790,7 +793,7 @@ void setAllTags(final TagMap.Ledger ledger) { synchronized (unsafeTags) { for (final TagMap.EntryChange entryChange : ledger) { if (entryChange.isRemoval()) { - unsafeTags.removeEntry(entryChange.tag()); + unsafeTags.remove(entryChange.tag()); } else { TagMap.Entry entry = (TagMap.Entry) entryChange; @@ -798,7 +801,7 @@ void setAllTags(final TagMap.Ledger ledger) { Object value = entry.objectValue(); if (!tagInterceptor.interceptTag(this, tag, value)) { - unsafeTags.putEntry(entry); + unsafeTags.set(entry); } } } 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 79db1f22998..87024d057bd 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,22 +2,20 @@ 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 implements TagsPostProcessor { +public class IntegrationAdder extends TagsPostProcessor { @Override - public Map processTags( - Map unsafeTags, DDSpanContext spanContext, List spanLinks) { + public void processTags( + TagMap unsafeTags, DDSpanContext spanContext, List spanLinks) { final CharSequence instrumentationName = spanContext.getIntegrationName(); if (instrumentationName != null) { - unsafeTags.put(DD_INTEGRATION, instrumentationName); + unsafeTags.set(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 00c299e36fe..a52fa938c3e 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 @@ -79,7 +79,7 @@ public void processTags( RedactionRules redactionRules = tagPrefixRedactionRules.getValue(); Object tagValue = unsafeTags.getObject(tagPrefix); if (tagValue instanceof PayloadTagsData) { - if (unsafeTags.remove(tagPrefix) != null) { + if (unsafeTags.remove(tagPrefix)) { spanMaxTags -= 1; } diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy index 3761944b6ba..c3398abeb43 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/datastreams/DefaultPathwayContextTest.groovy @@ -3,6 +3,7 @@ package datadog.trace.core.datastreams import datadog.communication.ddagent.DDAgentFeaturesDiscovery import datadog.trace.api.Config import datadog.trace.api.DDTraceId +import datadog.trace.api.TagMap import datadog.trace.api.TraceConfig import datadog.trace.api.WellKnownTags import datadog.trace.api.datastreams.StatsPoint @@ -454,7 +455,7 @@ class DefaultPathwayContextTest extends DDCoreSpecification { Map carrier = [(PROPAGATION_KEY_BASE64): encoded, "someotherkey": "someothervalue"] def contextVisitor = new Base64MapContextVisitor() - def spanContext = new ExtractedContext(DDTraceId.ONE, 1, 0, null, 0, null, null, null, null, localTraceConfig, DATADOG) + def spanContext = new ExtractedContext(DDTraceId.ONE, 1, 0, null, 0, null, (TagMap)null, null, null, localTraceConfig, DATADOG) def baseContext = AgentSpan.fromSpanContext(spanContext).storeInto(root()) def propagator = dataStreams.propagator() @@ -549,7 +550,7 @@ class DefaultPathwayContextTest extends DDCoreSpecification { def encoded = context.encode() Map carrier = [(PROPAGATION_KEY_BASE64): encoded, "someotherkey": "someothervalue"] def contextVisitor = new Base64MapContextVisitor() - def spanContext = new ExtractedContext(DDTraceId.ONE, 1, 0, null, 0, null, null, null, null, null, DATADOG) + def spanContext = new ExtractedContext(DDTraceId.ONE, 1, 0, null, 0, null, (TagMap)null, null, null, null, DATADOG) def baseContext = AgentSpan.fromSpanContext(spanContext).storeInto(root()) def propagator = dataStreams.propagator() diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 293a1ad830f..6140f449a7d 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -3657,9 +3657,7 @@ public boolean isJdkSocketEnabled() { public TagMap getLocalRootSpanTags() { final Map runtimeTags = getRuntimeTags(); - final TagMap result = new TagMap(); - result.putAll(runtimeTags); - + final TagMap result = TagMap.fromMap(runtimeTags); result.put(LANGUAGE_TAG_KEY, LANGUAGE_TAG_VALUE); result.put(SCHEMA_VERSION_TAG_KEY, SpanNaming.instance().version()); result.put(DDTags.PROFILING_ENABLED, isProfilingEnabled() ? 1 : 0); diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 6f4b709816b..4610f4970d8 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1,20 +1,24 @@ package datadog.trace.api; -import datadog.trace.api.function.TriConsumer; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import datadog.trace.api.function.TriConsumer; + /** * A super simple hash map designed for... * @@ -60,1561 +64,1853 @@ * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be * removed from the collision chain. */ -public final class TagMap implements Map, Iterable { +public interface TagMap extends Map, Iterable { /** Immutable empty TagMap - similar to {@link Collections#emptyMap()} */ - public static final TagMap EMPTY = createEmpty(); - - private static final TagMap createEmpty() { - // Using special constructor that creates a frozen view of an existing array - // Bucket calculation requires that array length is a power of 2 - // e.g. size 0 will not work, it results in ArrayIndexOutOfBoundsException, but size 1 does - return new TagMap(new Object[1], 0); - } - - /** Creates a new TagMap.Ledger */ - public static final Ledger ledger() { - return new Ledger(); - } - - /** Creates a new TagMap.Ledger which handles size modifications before expansion */ - public static final Ledger ledger(int size) { - return new Ledger(size); - } + public static final TagMap EMPTY = TagMapFactory.INSTANCE.empty(); /** Creates a new mutable TagMap that contains the contents of map */ - public static final TagMap fromMap(Map map) { - TagMap tagMap = new TagMap(); + public static TagMap fromMap(Map map) { + TagMap tagMap = TagMap.create(map.size()); tagMap.putAll(map); return tagMap; } /** Creates a new immutable TagMap that contains the contents of map */ - public static final TagMap fromMapImmutable(Map map) { + public static TagMap fromMapImmutable(Map map) { if (map.isEmpty()) { return TagMap.EMPTY; } else { return fromMap(map).freeze(); } } - - private final Object[] buckets; - private int size; - private boolean frozen; - - public TagMap() { - // needs to be a power of 2 for bucket masking calculation to work as intended - this.buckets = new Object[1 << 4]; - this.size = 0; - this.frozen = false; - } - - /** Used for inexpensive immutable */ - private TagMap(Object[] buckets, int size) { - this.buckets = buckets; - this.size = size; - this.frozen = true; - } - - @Override - public final int size() { - return this.size; - } - - @Override - public final boolean isEmpty() { - return (this.size == 0); + + static TagMap create() { + return TagMapFactory.INSTANCE.create(); } - - @Override - public boolean containsKey(Object key) { - if (!(key instanceof String)) return false; - - return (this.getEntry((String) key) != null); + + static TagMap create(int size) { + return TagMapFactory.INSTANCE.create(size); } - - @Override - public boolean containsValue(Object value) { - for (Entry entry : this) { - if (entry.objectValue().equals(value)) return true; - } - return false; + + /** Creates a new TagMap.Ledger */ + public static Ledger ledger() { + return new Ledger(); } - @Deprecated - @Override - public Set keySet() { - return new Keys(this); + /** Creates a new TagMap.Ledger which handles size modifications before expansion */ + public static Ledger ledger(int size) { + return new Ledger(size); } - + + boolean isOptimized(); + @Deprecated - @Override - public Collection values() { - return new Values(this); - } - + Set keySet(); + @Deprecated - @Override - public Set> entrySet() { - return new Entries(this); - } + Collection values(); + // @Deprecated -- not deprecated until OptimizedTagMap becomes the default + Set> entrySet(); + @Deprecated - @Override - public final Object get(Object tag) { - if (!(tag instanceof String)) return null; - - return this.getObject((String) tag); - } + Object get(Object tag); /** Provides the corresponding entry value as an Object - boxing if necessary */ - public final Object getObject(String tag) { - Entry entry = this.getEntry(tag); - return entry == null ? null : entry.objectValue(); - } + Object getObject(String tag); /** Provides the corresponding entry value as a String - calling toString if necessary */ - public final String getString(String tag) { - Entry entry = this.getEntry(tag); - return entry == null ? null : entry.stringValue(); - } + String getString(String tag); - public final boolean getBoolean(String tag) { - Entry entry = this.getEntry(tag); - return entry == null ? false : entry.booleanValue(); - } + boolean getBoolean(String tag); - public final int getInt(String tag) { - Entry entry = this.getEntry(tag); - return entry == null ? 0 : entry.intValue(); - } + int getInt(String tag); - public final long getLong(String tag) { - Entry entry = this.getEntry(tag); - return entry == null ? 0L : entry.longValue(); - } + long getLong(String tag); - public final float getFloat(String tag) { - Entry entry = this.getEntry(tag); - return entry == null ? 0F : entry.floatValue(); - } + float getFloat(String tag); + + double getDouble(String tag); - public final double getDouble(String tag) { - Entry entry = this.getEntry(tag); - return entry == null ? 0D : entry.doubleValue(); - } /** - * Provides the corresponding Entry object - preferable if the Entry needs to have its type - * checked + * Provides the corresponding Entry object - preferable w/ optimized TagMap if the Entry needs + * to have its type checked */ - public final Entry getEntry(String tag) { - Object[] thisBuckets = this.buckets; - - int hash = _hash(tag); - int bucketIndex = hash & (thisBuckets.length - 1); - - Object bucket = thisBuckets[bucketIndex]; - if (bucket == null) { - return null; - } else if (bucket instanceof Entry) { - Entry tagEntry = (Entry) bucket; - if (tagEntry.matches(tag)) return tagEntry; - } else if (bucket instanceof BucketGroup) { - BucketGroup lastGroup = (BucketGroup) bucket; - - Entry tagEntry = lastGroup.findInChain(hash, tag); - if (tagEntry != null) return tagEntry; - } - return null; - } - + Entry getEntry(String tag); + @Deprecated - public final Object put(String tag, Object value) { - TagMap.Entry entry = this.putEntry(Entry.newAnyEntry(tag, value)); - return entry == null ? null : entry.objectValue(); - } + Object put(String tag, Object value); /** - * Similar to {@link Map#put(Object, Object)}, but returns the prior Entry rather than the prior - * value - * - *

        Preferred to put because avoids having to box prior primitive value + * Sets value without returning prior value - optimal for legacy & optimized implementations */ - public final Entry set(String tag, Object value) { - return this.putEntry(Entry.newAnyEntry(tag, value)); - } + void set(String tag, Object value); /** * Similar to {@link TagMap#set(String, Object)} but more efficient when working with * CharSequences and Strings. Depending on this situation, this methods avoids having to do type * resolution later on */ - public final Entry set(String tag, CharSequence value) { - return this.putEntry(Entry.newObjectEntry(tag, value)); - } + void set(String tag, CharSequence value); - public final Entry set(String tag, boolean value) { - return this.putEntry(Entry.newBooleanEntry(tag, value)); - } + void set(String tag, boolean value); - public final Entry set(String tag, int value) { - return this.putEntry(Entry.newIntEntry(tag, value)); - } + void set(String tag, int value); - public final Entry set(String tag, long value) { - return this.putEntry(Entry.newLongEntry(tag, value)); - } + void set(String tag, long value); - public final Entry set(String tag, float value) { - return this.putEntry(Entry.newFloatEntry(tag, value)); - } + void set(String tag, float value); + + void set(String tag, double value); + + void set(Entry newEntry); + + /** + * sets the value while returning the prior Entry + */ + Entry getAndSet(String tag, Object value); + + Entry getAndSet(String tag, CharSequence value); + + Entry getAndSet(String tag, boolean value); + + Entry getAndSet(String tag, int value); + + Entry getAndSet(String tag, long value); + + Entry getAndSet(String tag, float value); + + Entry getAndSet(String tag, double value); + - public final Entry set(String tag, double value) { - return this.putEntry(Entry.newDoubleEntry(tag, value)); - } + /** + * TagMap specific method that places an Entry directly into an optimized TagMap avoiding + * need to allocate a new Entry object + */ + Entry getAndSet(Entry newEntry); + + void putAll(Map map); + + /** + * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from one TagMap to another + * + *

        For optimized TagMaps, this method takes advantage of the consistent TagMap layout to + * quickly handle each bucket. And similar to {@link TagMap#(Entry)} this method shares + * Entry objects from the source TagMap + */ + void putAll(TagMap that); + + void fillMap(Map map); + + void fillStringMap(Map stringMap); + + @Deprecated + Object remove(Object tag); + + /** + * Similar to {@link Map#remove(Object)} but doesn't return the prior value (orEntry). + * Preferred when prior value isn't needed - best for both legacy and optimal TagMaps + */ + boolean remove(String tag); /** - * TagMap specific method that places an Entry directly into the TagMap avoiding needing to - * allocate a new Entry object + * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior + * value. For optimized TagMap-s, this preferred because it avoids additional boxing. */ - public final Entry putEntry(Entry newEntry) { - this.checkWriteAccess(); + Entry getAndRemove(String tag); - Object[] thisBuckets = this.buckets; + /** Returns a mutable copy of this TagMap */ + TagMap copy(); + + /** + * Returns an immutable copy of this TagMap This method is more efficient than + * map.copy().freeze() when called on an immutable TagMap + */ + TagMap immutableCopy(); - int newHash = newEntry.hash(); - int bucketIndex = newHash & (thisBuckets.length - 1); + /** + * Provides an Iterator over the Entry-s of the TagMap Equivalent to entrySet().iterator() + * , but with less allocation + */ + @Override + Iterator iterator(); - Object bucket = thisBuckets[bucketIndex]; - if (bucket == null) { - thisBuckets[bucketIndex] = newEntry; + Stream stream(); - this.size += 1; - return null; - } else if (bucket instanceof Entry) { - Entry existingEntry = (Entry) bucket; - if (existingEntry.matches(newEntry.tag)) { - thisBuckets[bucketIndex] = newEntry; + /** + * Visits each Entry in this TagMap This method is more efficient than {@link TagMap#iterator()} + */ + void forEach(Consumer consumer); - // replaced existing entry - no size change - return existingEntry; - } else { - thisBuckets[bucketIndex] = - new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry); + /** + * Version of forEach that takes an extra context object that is passed as the first argument to + * the consumer + * + *

        The intention is to use this method to avoid using a capturing lambda + */ + void forEach(T thisObj, BiConsumer consumer); - this.size += 1; - return null; - } - } else if (bucket instanceof BucketGroup) { - BucketGroup lastGroup = (BucketGroup) bucket; + /** + * Version of forEach that takes two extra context objects that are passed as the first two + * argument to the consumer + * + *

        The intention is to use this method to avoid using a capturing lambda + */ + void forEach(T thisObj, U otherObj, TriConsumer consumer); - BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag); - if (containingGroup != null) { - // replaced existing entry - no size change - return containingGroup._replace(newHash, newEntry); - } + /** Clears the TagMap */ + void clear(); - if (!lastGroup.insertInChain(newHash, newEntry)) { - thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup); - } - this.size += 1; - return null; - } + /** Freeze the TagMap preventing further modification - returns this TagMap */ + TagMap freeze(); - // unreachable - return null; - } + /** Indicates if this map is frozen */ + boolean isFrozen(); - public final void putAll(Iterable entries) { - this.checkWriteAccess(); + /** Checks if the TagMap is writable - if not throws {@link IllegalStateException} */ + void checkWriteAccess(); - for (Entry tagEntry : entries) { - if (tagEntry.isRemoval()) { - this.remove(tagEntry.tag); - } else { - this.putEntry(tagEntry); - } + public abstract static class EntryChange { + public static final EntryRemoval newRemoval(String tag) { + return new EntryRemoval(tag); } - } - public final void putAll(TagMap.Ledger ledger) { - putAll(ledger.entryChanges, ledger.nextPos); - } - - private final void putAll(EntryChange[] entryChanges, int size) { - for (int i = 0; i < size && i < entryChanges.length; ++i) { - EntryChange change = entryChanges[i]; + final String tag; - if (change.isRemoval()) { - this.remove(change.tag()); - } else { - this.putEntry((Entry) change); - } + EntryChange(String tag) { + this.tag = tag; } - } - public final void putAll(Map map) { - this.checkWriteAccess(); + public final String tag() { + return this.tag; + } - if (map instanceof TagMap) { - this.putAll((TagMap) map); - } else { - for (Map.Entry entry : map.entrySet()) { - // use set which returns a prior Entry rather put which may box a prior primitive value - this.set(entry.getKey(), entry.getValue()); - } + public final boolean matches(String tag) { + return this.tag.equals(tag); } + + public abstract boolean isRemoval(); } - /** - * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from one TagMap to another - * - *

        This method takes advantage of the consistent TagMap layout to optimize the handling of each - * bucket. And similar to {@link TagMap#putEntry(Entry)} this method shares Entry objects from the - * source TagMap - */ - public final void putAll(TagMap that) { - this.checkWriteAccess(); + public static final class EntryRemoval extends EntryChange { + EntryRemoval(String tag) { + super(tag); + } - if (this.size == 0) { - this.putAllIntoEmptyMap(that); - } else { - this.putAllMerge(that); + @Override + public final boolean isRemoval() { + return true; } } - private final void putAllMerge(TagMap that) { - Object[] thisBuckets = this.buckets; - Object[] thatBuckets = that.buckets; + public static final class Entry extends EntryChange implements Map.Entry { + /* + * Special value used for Objects that haven't been type checked yet. + * These objects might be primitive box objects. + */ + public static final byte ANY = 0; + public static final byte OBJECT = 1; - // Since TagMap-s don't support expansion, buckets are perfectly aligned - // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check - // elimination - for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { - Object thatBucket = thatBuckets[i]; + /* + * Non-numeric primitive types + */ + public static final byte BOOLEAN = 2; + public static final byte CHAR = 3; - // if nothing incoming, nothing to do - if (thatBucket == null) continue; + /* + * Numeric constants - deliberately arranged to allow for checking by using type >= BYTE + */ + public static final byte BYTE = 4; + public static final byte SHORT = 5; + public static final byte INT = 6; + public static final byte LONG = 7; + public static final byte FLOAT = 8; + public static final byte DOUBLE = 9; + + static final Entry newAnyEntry(Map.Entry entry) { + return newAnyEntry(entry.getKey(), entry.getValue()); + } - Object thisBucket = thisBuckets[i]; - if (thisBucket == null) { - // This bucket is null, easy case - // Either copy over the sole entry or clone the BucketGroup chain + static final Entry newAnyEntry(String tag, Object value) { + // DQH - To keep entry creation (e.g. map changes) as fast as possible, + // the entry construction is kept as simple as possible. - if (thatBucket instanceof Entry) { - thisBuckets[i] = thatBucket; - this.size += 1; - } else if (thatBucket instanceof BucketGroup) { - BucketGroup thatGroup = (BucketGroup) thatBucket; + // Prior versions of this code did type detection on value to + // recognize box types but that proved expensive. So now, + // the type is recorded as an ANY which is an indicator to do + // type detection later if need be. + return new Entry(tag, ANY, 0L, value); + } - BucketGroup thisNewGroup = thatGroup.cloneChain(); - thisBuckets[i] = thisNewGroup; - this.size += thisNewGroup.sizeInChain(); - } - } else if (thisBucket instanceof Entry) { - // This bucket is a single entry, medium complexity case - // If other side is an Entry - just merge the entries into a bucket - // If other side is a BucketGroup - then clone the group and insert the entry normally into - // the cloned group + static final Entry newObjectEntry(String tag, Object value) { + return new Entry(tag, OBJECT, 0, value); + } - Entry thisEntry = (Entry) thisBucket; - int thisHash = thisEntry.hash(); + static final Entry newBooleanEntry(String tag, boolean value) { + return new Entry(tag, BOOLEAN, boolean2Prim(value), Boolean.valueOf(value)); + } - if (thatBucket instanceof Entry) { - Entry thatEntry = (Entry) thatBucket; - int thatHash = thatEntry.hash(); + static final Entry newBooleanEntry(String tag, Boolean box) { + return new Entry(tag, BOOLEAN, boolean2Prim(box.booleanValue()), box); + } - if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { - thisBuckets[i] = thatEntry; - // replacing entry, no size change - } else { - thisBuckets[i] = - new BucketGroup( - thisHash, thisEntry, - thatHash, thatEntry); - this.size += 1; - } - } else if (thatBucket instanceof BucketGroup) { - BucketGroup thatGroup = (BucketGroup) thatBucket; + static final Entry newIntEntry(String tag, int value) { + return new Entry(tag, INT, int2Prim(value), null); + } - // Clone the other group, then place this entry into that group - BucketGroup thisNewGroup = thatGroup.cloneChain(); - int thisNewGroupSize = thisNewGroup.sizeInChain(); + static final Entry newIntEntry(String tag, Integer box) { + return new Entry(tag, INT, int2Prim(box.intValue()), box); + } - Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag()); - if (incomingEntry != null) { - // there's already an entry w/ the same tag from the incoming TagMap - // incoming entry clobbers the existing try, so we're done - thisBuckets[i] = thisNewGroup; + static final Entry newLongEntry(String tag, long value) { + return new Entry(tag, LONG, long2Prim(value), null); + } - // overlapping group - subtract one for clobbered existing entry - this.size += thisNewGroupSize - 1; - } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) { - // able to add thisEntry into the existing groups - thisBuckets[i] = thisNewGroup; + static final Entry newLongEntry(String tag, Long box) { + return new Entry(tag, LONG, long2Prim(box.longValue()), box); + } - // non overlapping group - existing entry already accounted for in this.size - this.size += thisNewGroupSize; - } else { - // unable to add into the existing groups - thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); + static final Entry newFloatEntry(String tag, float value) { + return new Entry(tag, FLOAT, float2Prim(value), null); + } - // non overlapping group - existing entry already accounted for in this.size - this.size += thisNewGroupSize; - } - } - } else if (thisBucket instanceof BucketGroup) { - // This bucket is a BucketGroup, medium to hard case - // If the other side is an entry, just normal insertion procedure - no cloning required - BucketGroup thisGroup = (BucketGroup) thisBucket; + static final Entry newFloatEntry(String tag, Float box) { + return new Entry(tag, FLOAT, float2Prim(box.floatValue()), box); + } - if (thatBucket instanceof Entry) { - Entry thatEntry = (Entry) thatBucket; - int thatHash = thatEntry.hash(); + static final Entry newDoubleEntry(String tag, double value) { + return new Entry(tag, DOUBLE, double2Prim(value), null); + } - if (thisGroup.replaceInChain(thatHash, thatEntry) != null) { - // replaced existing entry no size change - } else if (thisGroup.insertInChain(thatHash, thatEntry)) { - this.size += 1; - } else { - thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup); - this.size += 1; - } - } else if (thatBucket instanceof BucketGroup) { - // Most complicated case - need to walk that bucket group chain and update this chain - BucketGroup thatGroup = (BucketGroup) thatBucket; + static final Entry newDoubleEntry(String tag, Double box) { + return new Entry(tag, DOUBLE, double2Prim(box.doubleValue()), box); + } - // Taking the easy / expensive way out for updating size - int thisPrevGroupSize = thisGroup.sizeInChain(); + /* + * hash is stored in line for fast handling of Entry-s coming from another TagMap + * However, hash is lazily computed using the same trick as {@link java.lang.String}. + */ + int lazyTagHash; - BucketGroup thisNewGroup = thisGroup.replaceOrInsertAllInChain(thatGroup); - int thisNewGroupSize = thisNewGroup.sizeInChain(); + // To optimize construction of Entry around boxed primitives and Object entries, + // no type checks are done during construction. + // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into + // obj + // If an ANY entry is later type checked or request as a primitive, then the ANY will be + // resolved + // to the correct type. - thisBuckets[i] = thisNewGroup; - this.size += (thisNewGroupSize - thisPrevGroupSize); - } + // From the outside perspective, this object remains functionally immutable. + // However, internally, it is important to remember that this type must be thread safe. + // That includes multiple threads racing to resolve an ANY entry at the same time. + + // Type and prim cannot use the same trick as hash because during ANY resolution the order of + // writes is important + volatile byte rawType; + volatile long rawPrim; + volatile Object rawObj; + + volatile String strCache = null; + + private Entry(String tag, byte type, long prim, Object obj) { + super(tag); + this.lazyTagHash = 0; // lazily computed + + this.rawType = type; + this.rawPrim = prim; + this.rawObj = obj; + } + + int hash() { + // If value of hash read in this thread is zero, then hash is computed. + // hash is not held as a volatile, since this computation can safely be repeated as any time + int hash = this.lazyTagHash; + if (hash != 0) return hash; + + hash = _hash(this.tag); + this.lazyTagHash = hash; + return hash; + } + + public final byte type() { + return this.resolveAny(); + } + + public final boolean is(byte type) { + byte curType = this.rawType; + if (curType == type) { + return true; + } else if (curType != ANY) { + return false; + } else { + return (this.resolveAny() == type); + } + } + + public final boolean isNumericPrimitive() { + byte curType = this.rawType; + if (_isNumericPrimitive(curType)) { + return true; + } else if (curType != ANY) { + return false; + } else { + return _isNumericPrimitive(this.resolveAny()); + } + } + + public final boolean isNumber() { + byte curType = this.rawType; + return _isNumericPrimitive(curType) || (this.rawObj instanceof Number); + } + + private static final boolean _isNumericPrimitive(byte type) { + return (type >= BYTE); + } + + private final byte resolveAny() { + byte curType = this.rawType; + if (curType != ANY) return curType; + + Object value = this.rawObj; + long prim; + byte resolvedType; + + if (value instanceof Boolean) { + Boolean boolValue = (Boolean) value; + prim = boolean2Prim(boolValue); + resolvedType = BOOLEAN; + } else if (value instanceof Integer) { + Integer intValue = (Integer) value; + prim = int2Prim(intValue); + resolvedType = INT; + } else if (value instanceof Long) { + Long longValue = (Long) value; + prim = long2Prim(longValue); + resolvedType = LONG; + } else if (value instanceof Float) { + Float floatValue = (Float) value; + prim = float2Prim(floatValue); + resolvedType = FLOAT; + } else if (value instanceof Double) { + Double doubleValue = (Double) value; + prim = double2Prim(doubleValue); + resolvedType = DOUBLE; + } else { + prim = 0; + resolvedType = OBJECT; + } + + this._setPrim(resolvedType, prim); + + return resolvedType; + } + + private void _setPrim(byte type, long prim) { + // Order is important here, the contract is that prim must be set properly *before* + // type is set to a non-object type + + this.rawPrim = prim; + this.rawType = type; + } + + public final boolean isObject() { + return this.is(OBJECT); + } + + public final boolean isRemoval() { + return false; + } + + public final Object objectValue() { + if (this.rawObj != null) { + return this.rawObj; + } + + // This code doesn't need to handle ANY-s. + // An entry that starts as an ANY will always have this.obj set + switch (this.rawType) { + case BOOLEAN: + this.rawObj = prim2Boolean(this.rawPrim); + break; + + case INT: + // Maybe use a wider cache that handles response code??? + this.rawObj = prim2Int(this.rawPrim); + break; + + case LONG: + this.rawObj = prim2Long(this.rawPrim); + break; + + case FLOAT: + this.rawObj = prim2Float(this.rawPrim); + break; + + case DOUBLE: + this.rawObj = prim2Double(this.rawPrim); + break; + + default: + // DQH - satisfy spot bugs + break; + } + + return this.rawObj; + } + + public final boolean booleanValue() { + byte type = this.rawType; + + if (type == BOOLEAN) { + return prim2Boolean(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Boolean) { + boolean boolValue = (Boolean) this.rawObj; + this._setPrim(BOOLEAN, boolean2Prim(boolValue)); + return boolValue; } - } - } - /* - * Specially optimized version of putAll for the common case of destination map being empty - */ - private final void putAllIntoEmptyMap(TagMap that) { - Object[] thisBuckets = this.buckets; - Object[] thatBuckets = that.buckets; + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.rawPrim; - // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check - // elimination - for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { - Object thatBucket = thatBuckets[i]; + switch (resolvedType) { + case INT: + return prim2Int(prim) != 0; - // faster to explicitly null check first, then do instanceof - if (thatBucket == null) { - // do nothing - } else if (thatBucket instanceof BucketGroup) { - // if it is a BucketGroup, then need to clone - BucketGroup thatGroup = (BucketGroup) thatBucket; + case LONG: + return prim2Long(prim) != 0L; - thisBuckets[i] = thatGroup.cloneChain(); - } else { // if ( thatBucket instanceof Entry ) - thisBuckets[i] = thatBucket; - } - } - this.size = that.size; - } + case FLOAT: + return prim2Float(prim) != 0F; - public final void fillMap(Map map) { - Object[] thisBuckets = this.buckets; + case DOUBLE: + return prim2Double(prim) != 0D; - for (int i = 0; i < thisBuckets.length; ++i) { - Object thisBucket = thisBuckets[i]; + case OBJECT: + return (this.rawObj != null); + } - if (thisBucket instanceof Entry) { - Entry thisEntry = (Entry) thisBucket; + return false; + } - map.put(thisEntry.tag, thisEntry.objectValue()); - } else if (thisBucket instanceof BucketGroup) { - BucketGroup thisGroup = (BucketGroup) thisBucket; + public final int intValue() { + byte type = this.rawType; - thisGroup.fillMapFromChain(map); + if (type == INT) { + return prim2Int(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Integer) { + int intValue = (Integer) this.rawObj; + this._setPrim(INT, int2Prim(intValue)); + return intValue; } - } - } - public final void fillStringMap(Map stringMap) { - Object[] thisBuckets = this.buckets; + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.rawPrim; - for (int i = 0; i < thisBuckets.length; ++i) { - Object thisBucket = thisBuckets[i]; + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1 : 0; - if (thisBucket instanceof Entry) { - Entry thisEntry = (Entry) thisBucket; + case LONG: + return (int) prim2Long(prim); - stringMap.put(thisEntry.tag, thisEntry.stringValue()); - } else if (thisBucket instanceof BucketGroup) { - BucketGroup thisGroup = (BucketGroup) thisBucket; + case FLOAT: + return (int) prim2Float(prim); - thisGroup.fillStringMapFromChain(stringMap); + case DOUBLE: + return (int) prim2Double(prim); + + case OBJECT: + return 0; } - } - } - @Deprecated - @Override - public final Object remove(Object tag) { - if (!(tag instanceof String)) return null; + return 0; + } - Entry entry = this.removeEntry((String) tag); - return entry == null ? null : entry.objectValue(); - } + public final long longValue() { + byte type = this.rawType; - /** - * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior - * value This is preferred because it avoids boxing a prior primitive value - */ - public final Entry removeEntry(String tag) { - this.checkWriteAccess(); + if (type == LONG) { + return prim2Long(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Long) { + long longValue = (Long) this.rawObj; + this._setPrim(LONG, long2Prim(longValue)); + return longValue; + } - Object[] thisBuckets = this.buckets; + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.rawPrim; - int hash = _hash(tag); - int bucketIndex = hash & (thisBuckets.length - 1); + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1L : 0L; - Object bucket = thisBuckets[bucketIndex]; - // null bucket case - do nothing - if (bucket instanceof Entry) { - Entry existingEntry = (Entry) bucket; - if (existingEntry.matches(tag)) { - thisBuckets[bucketIndex] = null; + case INT: + return (long) prim2Int(prim); - this.size -= 1; - return existingEntry; - } else { - return null; - } - } else if (bucket instanceof BucketGroup) { - BucketGroup lastGroup = (BucketGroup) bucket; + case FLOAT: + return (long) prim2Float(prim); - BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag); - if (containingGroup == null) { - return null; - } + case DOUBLE: + return (long) prim2Double(prim); - Entry existingEntry = containingGroup._remove(hash, tag); - if (containingGroup._isEmpty()) { - this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup); + case OBJECT: + return 0; } - this.size -= 1; - return existingEntry; + return 0; } - return null; - } - - /** Returns a mutable copy of this TagMap */ - public final TagMap copy() { - TagMap copy = new TagMap(); - copy.putAllIntoEmptyMap(this); - return copy; - } - /** - * Returns an immutable copy of this TagMap This method is more efficient than - * map.copy().freeze() when called on an immutable TagMap - */ - public final TagMap immutableCopy() { - if (this.frozen) { - return this; - } else { - return this.copy().freeze(); - } - } + public final float floatValue() { + byte type = this.rawType; - /** - * Provides an Iterator over the Entry-s of the TagMap Equivalent to entrySet().iterator() - * , but with less allocation - */ - @Override - public final Iterator iterator() { - return new EntryIterator(this); - } + if (type == FLOAT) { + return prim2Float(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Float) { + float floatValue = (Float) this.rawObj; + this._setPrim(FLOAT, float2Prim(floatValue)); + return floatValue; + } - public final Stream stream() { - return StreamSupport.stream(spliterator(), false); - } + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.rawPrim; - /** - * Visits each Entry in this TagMap This method is more efficient than {@link TagMap#iterator()} - */ - public final void forEach(Consumer consumer) { - Object[] thisBuckets = this.buckets; + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1F : 0F; - for (int i = 0; i < thisBuckets.length; ++i) { - Object thisBucket = thisBuckets[i]; + case INT: + return (float) prim2Int(prim); - if (thisBucket instanceof Entry) { - Entry thisEntry = (Entry) thisBucket; + case LONG: + return (float) prim2Long(prim); - consumer.accept(thisEntry); - } else if (thisBucket instanceof BucketGroup) { - BucketGroup thisGroup = (BucketGroup) thisBucket; + case DOUBLE: + return (float) prim2Double(prim); - thisGroup.forEachInChain(consumer); + case OBJECT: + return 0F; } - } - } - - /** - * Version of forEach that takes an extra context object that is passed as the first argument to - * the consumer - * - *

        The intention is to use this method to avoid using a capturing lambda - */ - public final void forEach(T thisObj, BiConsumer consumer) { - Object[] thisBuckets = this.buckets; - - for (int i = 0; i < thisBuckets.length; ++i) { - Object thisBucket = thisBuckets[i]; - if (thisBucket instanceof Entry) { - Entry thisEntry = (Entry) thisBucket; + return 0F; + } - consumer.accept(thisObj, thisEntry); - } else if (thisBucket instanceof BucketGroup) { - BucketGroup thisGroup = (BucketGroup) thisBucket; + public final double doubleValue() { + byte type = this.rawType; - thisGroup.forEachInChain(thisObj, consumer); + if (type == DOUBLE) { + return prim2Double(this.rawPrim); + } else if (type == ANY && this.rawObj instanceof Double) { + double doubleValue = (Double) this.rawObj; + this._setPrim(DOUBLE, double2Prim(doubleValue)); + return doubleValue; } - } - } - /** - * Version of forEach that takes two extra context objects that are passed as the first two - * argument to the consumer - * - *

        The intention is to use this method to avoid using a capturing lambda - */ - public final void forEach( - T thisObj, U otherObj, TriConsumer consumer) { - Object[] thisBuckets = this.buckets; + // resolution will set prim if necessary + byte resolvedType = this.resolveAny(); + long prim = this.rawPrim; - for (int i = 0; i < thisBuckets.length; ++i) { - Object thisBucket = thisBuckets[i]; + switch (resolvedType) { + case BOOLEAN: + return prim2Boolean(prim) ? 1D : 0D; - if (thisBucket instanceof Entry) { - Entry thisEntry = (Entry) thisBucket; + case INT: + return (double) prim2Int(prim); - consumer.accept(thisObj, otherObj, thisEntry); - } else if (thisBucket instanceof BucketGroup) { - BucketGroup thisGroup = (BucketGroup) thisBucket; + case LONG: + return (double) prim2Long(prim); - thisGroup.forEachInChain(thisObj, otherObj, consumer); + case FLOAT: + return (double) prim2Float(prim); + + case OBJECT: + return 0D; } + + return 0D; } - } - /** Clears the TagMap */ - public final void clear() { - this.checkWriteAccess(); + public final String stringValue() { + String strCache = this.strCache; + if (strCache != null) { + return strCache; + } - Arrays.fill(this.buckets, null); - this.size = 0; - } + String computeStr = this.computeStringValue(); + this.strCache = computeStr; + return computeStr; + } - /** Freeze the TagMap preventing further modification - returns this TagMap */ - public final TagMap freeze() { - this.frozen = true; + private final String computeStringValue() { + // Could do type resolution here, + // but decided to just fallback to this.obj.toString() for ANY case + switch (this.rawType) { + case BOOLEAN: + return Boolean.toString(prim2Boolean(this.rawPrim)); - return this; - } + case INT: + return Integer.toString(prim2Int(this.rawPrim)); - /** Indicates if this map is frozen */ - public boolean isFrozen() { - return this.frozen; - } + case LONG: + return Long.toString(prim2Long(this.rawPrim)); - /** Checks if the TagMap is writable - if not throws {@link IllegalStateException} */ - public final void checkWriteAccess() { - if (this.frozen) throw new IllegalStateException("TagMap frozen"); - } + case FLOAT: + return Float.toString(prim2Float(this.rawPrim)); - final void checkIntegrity() { - // Decided to use if ( cond ) throw new IllegalStateException rather than assert - // That was done to avoid the extra static initialization needed for an assertion - // While that's probably an unnecessary optimization, this method is only called in tests + case DOUBLE: + return Double.toString(prim2Double(this.rawPrim)); - Object[] thisBuckets = this.buckets; + case OBJECT: + case ANY: + return this.rawObj.toString(); + } - for (int i = 0; i < thisBuckets.length; ++i) { - Object thisBucket = thisBuckets[i]; + return null; + } - if (thisBucket instanceof Entry) { - Entry thisEntry = (Entry) thisBucket; - int thisHash = thisEntry.hash(); + @Override + public final String toString() { + return this.tag() + '=' + this.stringValue(); + } - int expectedBucket = thisHash & (thisBuckets.length - 1); - if (expectedBucket != i) { - throw new IllegalStateException("incorrect bucket"); - } - } else if (thisBucket instanceof BucketGroup) { - BucketGroup thisGroup = (BucketGroup) thisBucket; + @Deprecated + @Override + public String getKey() { + return this.tag(); + } - for (BucketGroup curGroup = thisGroup; curGroup != null; curGroup = curGroup.prev) { - for (int j = 0; j < BucketGroup.LEN; ++j) { - Entry thisEntry = curGroup._entryAt(i); - if (thisEntry == null) continue; + @Deprecated + @Override + public Object getValue() { + return this.objectValue(); + } - int thisHash = thisEntry.hash(); - assert curGroup._hashAt(i) == thisHash; + @Deprecated + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public final int hashCode() { + return this.hash(); + } + + @Override + public boolean equals(Object obj) { + if ( !(obj instanceof TagMap.Entry) ) return false; + + TagMap.Entry that = (TagMap.Entry)obj; + return this.tag.equals(that.tag) && this.objectValue().equals(that.objectValue()); + } - int expectedBucket = thisHash & (thisBuckets.length - 1); - if (expectedBucket != i) { - throw new IllegalStateException("incorrect bucket"); - } - } - } - } + private static final long boolean2Prim(boolean value) { + return value ? 1L : 0L; } - if (this.size != this.computeSize()) { - throw new IllegalStateException("incorrect size"); + private static final boolean prim2Boolean(long prim) { + return (prim != 0L); } - if (this.isEmpty() != this.checkIfEmpty()) { - throw new IllegalStateException("incorrect empty status"); + + private static final long int2Prim(int value) { + return (long) value; } - } - final int computeSize() { - Object[] thisBuckets = this.buckets; + private static final int prim2Int(long prim) { + return (int) prim; + } - int size = 0; - for (int i = 0; i < thisBuckets.length; ++i) { - Object curBucket = thisBuckets[i]; + private static final long long2Prim(long value) { + return value; + } - if (curBucket instanceof Entry) { - size += 1; - } else if (curBucket instanceof BucketGroup) { - BucketGroup curGroup = (BucketGroup) curBucket; - size += curGroup.sizeInChain(); - } + private static final long prim2Long(long prim) { + return prim; } - return size; - } - final boolean checkIfEmpty() { - Object[] thisBuckets = this.buckets; + private static final long float2Prim(float value) { + return (long) Float.floatToIntBits(value); + } - for (int i = 0; i < thisBuckets.length; ++i) { - Object curBucket = thisBuckets[i]; + private static final float prim2Float(long prim) { + return Float.intBitsToFloat((int) prim); + } - if (curBucket instanceof Entry) { - return false; - } else if (curBucket instanceof BucketGroup) { - BucketGroup curGroup = (BucketGroup) curBucket; - if (!curGroup.isEmptyChain()) return false; - } + private static final long double2Prim(double value) { + return Double.doubleToRawLongBits(value); } - return true; - } + private static final double prim2Double(long prim) { + return Double.longBitsToDouble(prim); + } - @Override - public final String toString() { - return toPrettyString(); + static final int _hash(String tag) { + int hash = tag.hashCode(); + return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); + } } - /** - * Standard toString implementation - output is similar to {@link java.util.HashMap#toString()} + /* + * An in-order ledger of changes to be made to a TagMap. + * Ledger can also serves as a builder for TagMap-s via build & buildImmutable. */ - final String toPrettyString() { - boolean first = true; - - StringBuilder ledger = new StringBuilder(128); - ledger.append('{'); - for (Entry entry : this) { - if (first) { - first = false; - } else { - ledger.append(", "); - } + public static final class Ledger implements Iterable { + EntryChange[] entryChanges; + int nextPos = 0; + boolean containsRemovals = false; - ledger.append(entry.tag).append('=').append(entry.stringValue()); + private Ledger() { + this(8); } - ledger.append('}'); - return ledger.toString(); - } - /** - * toString that more visibility into the internal structure of TagMap - primarily for deep - * debugging - */ - final String toInternalString() { - Object[] thisBuckets = this.buckets; + private Ledger(int size) { + this.entryChanges = new EntryChange[size]; + } - StringBuilder ledger = new StringBuilder(128); - for (int i = 0; i < thisBuckets.length; ++i) { - ledger.append('[').append(i).append("] = "); + public final boolean isDefinitelyEmpty() { + return (this.nextPos == 0); + } - Object thisBucket = thisBuckets[i]; - if (thisBucket == null) { - ledger.append("null"); - } else if (thisBucket instanceof Entry) { - ledger.append('{').append(thisBucket).append('}'); - } else if (thisBucket instanceof BucketGroup) { - for (BucketGroup curGroup = (BucketGroup) thisBucket; - curGroup != null; - curGroup = curGroup.prev) { - ledger.append(curGroup).append(" -> "); - } - } - ledger.append('\n'); + /** + * Provides the estimated size of the map created by the ledger Doesn't account for overwritten + * entries or entry removal + * + * @return + */ + public final int estimateSize() { + return this.nextPos; } - return ledger.toString(); - } - static final int _hash(String tag) { - int hash = tag.hashCode(); - return hash == 0 ? 0xDD06 : hash ^ (hash >>> 16); - } + public final boolean containsRemovals() { + return this.containsRemovals; + } - public abstract static class EntryChange { - public static final EntryRemoval newRemoval(String tag) { - return new EntryRemoval(tag); + public final Ledger set(String tag, Object value) { + return this.recordEntry(Entry.newAnyEntry(tag, value)); + } + + public final Ledger set(String tag, CharSequence value) { + return this.recordEntry(Entry.newObjectEntry(tag, value)); } - final String tag; + public final Ledger set(String tag, boolean value) { + return this.recordEntry(Entry.newBooleanEntry(tag, value)); + } - EntryChange(String tag) { - this.tag = tag; + public final Ledger set(String tag, int value) { + return this.recordEntry(Entry.newIntEntry(tag, value)); } - public final String tag() { - return this.tag; + public final Ledger set(String tag, long value) { + return this.recordEntry(Entry.newLongEntry(tag, value)); } - public final boolean matches(String tag) { - return this.tag.equals(tag); + public final Ledger set(String tag, float value) { + return this.recordEntry(Entry.newFloatEntry(tag, value)); } - public abstract boolean isRemoval(); - } + public final Ledger set(String tag, double value) { + return this.recordEntry(Entry.newDoubleEntry(tag, value)); + } - public static final class EntryRemoval extends EntryChange { - EntryRemoval(String tag) { - super(tag); + public final Ledger set(Entry entry) { + return this.recordEntry(entry); } - @Override - public final boolean isRemoval() { - return true; + public final Ledger remove(String tag) { + return this.recordRemoval(EntryChange.newRemoval(tag)); } - } - public static final class Entry extends EntryChange implements Map.Entry { - /* - * Special value used for Objects that haven't been type checked yet. - * These objects might be primitive box objects. - */ - public static final byte ANY = 0; - public static final byte OBJECT = 1; + private final Ledger recordEntry(Entry entry) { + this.recordChange(entry); + return this; + } - /* - * Non-numeric primitive types - */ - public static final byte BOOLEAN = 2; - public static final byte CHAR = 3; + private final Ledger recordRemoval(EntryRemoval entry) { + this.recordChange(entry); + this.containsRemovals = true; - /* - * Numeric constants - deliberately arranged to allow for checking by using type >= BYTE - */ - public static final byte BYTE = 4; - public static final byte SHORT = 5; - public static final byte INT = 6; - public static final byte LONG = 7; - public static final byte FLOAT = 8; - public static final byte DOUBLE = 9; + return this; + } - static final Entry newAnyEntry(String tag, Object value) { - // DQH - To keep entry creation (e.g. map changes) as fast as possible, - // the entry construction is kept as simple as possible. + private final void recordChange(EntryChange entryChange) { + if (this.nextPos >= this.entryChanges.length) { + this.entryChanges = Arrays.copyOf(this.entryChanges, this.entryChanges.length << 1); + } - // Prior versions of this code did type detection on value to - // recognize box types but that proved expensive. So now, - // the type is recorded as an ANY which is an indicator to do - // type detection later if need be. - return new Entry(tag, ANY, 0L, value); + this.entryChanges[this.nextPos++] = entryChange; } - static final Entry newObjectEntry(String tag, Object value) { - return new Entry(tag, OBJECT, 0, value); + public final Ledger smartRemove(String tag) { + if (this.contains(tag)) { + this.remove(tag); + } + return this; } - static final Entry newBooleanEntry(String tag, boolean value) { - return new Entry(tag, BOOLEAN, boolean2Prim(value), Boolean.valueOf(value)); - } + private final boolean contains(String tag) { + EntryChange[] thisChanges = this.entryChanges; - static final Entry newBooleanEntry(String tag, Boolean box) { - return new Entry(tag, BOOLEAN, boolean2Prim(box.booleanValue()), box); + // min is to clamp, so bounds check elimination optimization works + int lenClamp = Math.min(this.nextPos, thisChanges.length); + for (int i = 0; i < lenClamp; ++i) { + if (thisChanges[i].matches(tag)) return true; + } + return false; } - static final Entry newIntEntry(String tag, int value) { - return new Entry(tag, INT, int2Prim(value), null); - } + /* + * Just for testing + */ + final Entry findLastEntry(String tag) { + EntryChange[] thisChanges = this.entryChanges; - static final Entry newIntEntry(String tag, Integer box) { - return new Entry(tag, INT, int2Prim(box.intValue()), box); + // min is to clamp, so ArrayBoundsCheckElimination optimization works + int clampLen = Math.min(this.nextPos, thisChanges.length) - 1; + for (int i = clampLen; i >= 0; --i) { + EntryChange thisChange = thisChanges[i]; + if (!thisChange.isRemoval() && thisChange.matches(tag)) return (Entry) thisChange; + } + return null; } - static final Entry newLongEntry(String tag, long value) { - return new Entry(tag, LONG, long2Prim(value), null); + public final void reset() { + Arrays.fill(this.entryChanges, null); + this.nextPos = 0; } - static final Entry newLongEntry(String tag, Long box) { - return new Entry(tag, LONG, long2Prim(box.longValue()), box); + @Override + public final Iterator iterator() { + return new IteratorImpl(this.entryChanges, this.nextPos); } - static final Entry newFloatEntry(String tag, float value) { - return new Entry(tag, FLOAT, float2Prim(value), null); + public TagMap build() { + TagMap map = TagMap.create(this.estimateSize()); + fill(map); + return map; } - - static final Entry newFloatEntry(String tag, Float box) { - return new Entry(tag, FLOAT, float2Prim(box.floatValue()), box); + + TagMap build(TagMapFactory mapFactory) { + TagMap map = mapFactory.create(this.estimateSize()); + fill(map); + return map; } - - static final Entry newDoubleEntry(String tag, double value) { - return new Entry(tag, DOUBLE, double2Prim(value), null); + + void fill(TagMap map) { + EntryChange[] entryChanges = this.entryChanges; + int size = this.nextPos; + for (int i = 0; i < size && i < entryChanges.length; ++i) { + EntryChange change = entryChanges[i]; + + if (change.isRemoval()) { + map.remove(change.tag()); + } else { + map.set((Entry) change); + } + } + } + + TagMap buildImmutable(TagMapFactory mapFactory) { + if (this.nextPos == 0) { + return mapFactory.empty(); + } else { + return this.build(mapFactory).freeze(); + } } - static final Entry newDoubleEntry(String tag, Double box) { - return new Entry(tag, DOUBLE, double2Prim(box.doubleValue()), box); + public TagMap buildImmutable() { + if (this.nextPos == 0) { + return TagMap.EMPTY; + } else { + return this.build().freeze(); + } } - /* - * hash is stored in line for fast handling of Entry-s coming another Tag - * However, hash is lazily computed using the same trick as {@link java.lang.String}. - */ - int lazyTagHash; + static final class IteratorImpl implements Iterator { + private final EntryChange[] entryChanges; + private final int size; - // To optimize construction of Entry around boxed primitives and Object entries, - // no type checks are done during construction. - // Any Object entries are initially marked as type ANY, prim set to 0, and the Object put into - // obj - // If an ANY entry is later type checked or request as a primitive, then the ANY will be - // resolved - // to the correct type. + private int pos; - // From the outside perspective, this object remains functionally immutable. - // However, internally, it is important to remember that this type must be thread safe. - // That includes multiple threads racing to resolve an ANY entry at the same time. + IteratorImpl(EntryChange[] entryChanges, int size) { + this.entryChanges = entryChanges; + this.size = size; - // Type and prim cannot use the same trick as hash because during ANY resolution the order of - // writes is important - volatile byte rawType; - volatile long rawPrim; - volatile Object rawObj; + this.pos = -1; + } - volatile String strCache = null; + @Override + public final boolean hasNext() { + return (this.pos + 1 < this.size); + } - private Entry(String tag, byte type, long prim, Object obj) { - super(tag); - this.lazyTagHash = 0; // lazily computed + @Override + public EntryChange next() { + if (!this.hasNext()) throw new NoSuchElementException("no next"); - this.rawType = type; - this.rawPrim = prim; - this.rawObj = obj; + return this.entryChanges[++this.pos]; + } } + } +} - int hash() { - // If value of hash read in this thread is zero, then hash is computed. - // hash is not held as a volatile, since this computation can safely be repeated as any time - int hash = this.lazyTagHash; - if (hash != 0) return hash; +/* + * Using a class, so class hierarchy analysis kicks in + * That will allow all of the calls to create methods to be devirtualized without a guard + */ +abstract class TagMapFactory { + public static final TagMapFactory INSTANCE = new OptimizedTagMapFactory(); + + public abstract MapT create(); + + public abstract MapT create(int size); + + public abstract MapT empty(); +} - hash = _hash(this.tag); - this.lazyTagHash = hash; - return hash; - } +final class OptimizedTagMapFactory extends TagMapFactory { + @Override + public final OptimizedTagMap create() { + return new OptimizedTagMap(); + } + + @Override + public OptimizedTagMap create(int size) { + return new OptimizedTagMap(); + } + + @Override + public OptimizedTagMap empty() { + return OptimizedTagMap.EMPTY; + } +} - public final byte type() { - return this.resolveAny(); - } +final class LegacyTagMapFactory extends TagMapFactory { + @Override + public final LegacyTagMap create() { + return new LegacyTagMap(); + } + + @Override + public LegacyTagMap create(int size) { + return new LegacyTagMap(size); + } + + @Override + public LegacyTagMap empty() { + return LegacyTagMap.EMPTY; + } +} - public final boolean is(byte type) { - byte curType = this.rawType; - if (curType == type) { - return true; - } else if (curType != ANY) { - return false; - } else { - return (this.resolveAny() == type); - } - } - public final boolean isNumericPrimitive() { - byte curType = this.rawType; - if (_isNumericPrimitive(curType)) { - return true; - } else if (curType != ANY) { - return false; - } else { - return _isNumericPrimitive(this.resolveAny()); - } - } +final class OptimizedTagMap implements TagMap { + // Using special constructor that creates a frozen view of an existing array + // Bucket calculation requires that array length is a power of 2 + // e.g. size 0 will not work, it results in ArrayIndexOutOfBoundsException, but size 1 does + static final OptimizedTagMap EMPTY = new OptimizedTagMap(new Object[1], 0); + + private final Object[] buckets; + private int size; + private boolean frozen; + + public OptimizedTagMap() { + // needs to be a power of 2 for bucket masking calculation to work as intended + this.buckets = new Object[1 << 4]; + this.size = 0; + this.frozen = false; + } + + /** Used for inexpensive immutable */ + private OptimizedTagMap(Object[] buckets, int size) { + this.buckets = buckets; + this.size = size; + this.frozen = true; + } + + @Override + public final boolean isOptimized() { + return true; + } + + @Override + public final int size() { + return this.size; + } + + @Override + public final boolean isEmpty() { + return (this.size == 0); + } - public final boolean isNumber() { - byte curType = this.rawType; - return _isNumericPrimitive(curType) || (this.rawObj instanceof Number); - } + @Deprecated + @Override + public final Object get(Object tag) { + if (!(tag instanceof String)) return null; - private static final boolean _isNumericPrimitive(byte type) { - return (type >= BYTE); - } + return this.getObject((String) tag); + } + + /** Provides the corresponding entry value as an Object - boxing if necessary */ + public final Object getObject(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? null : entry.objectValue(); + } - private final byte resolveAny() { - byte curType = this.rawType; - if (curType != ANY) return curType; + /** Provides the corresponding entry value as a String - calling toString if necessary */ + public final String getString(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? null : entry.stringValue(); + } - Object value = this.rawObj; - long prim; - byte resolvedType; + public final boolean getBoolean(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? false : entry.booleanValue(); + } - if (value instanceof Boolean) { - Boolean boolValue = (Boolean) value; - prim = boolean2Prim(boolValue); - resolvedType = BOOLEAN; - } else if (value instanceof Integer) { - Integer intValue = (Integer) value; - prim = int2Prim(intValue); - resolvedType = INT; - } else if (value instanceof Long) { - Long longValue = (Long) value; - prim = long2Prim(longValue); - resolvedType = LONG; - } else if (value instanceof Float) { - Float floatValue = (Float) value; - prim = float2Prim(floatValue); - resolvedType = FLOAT; - } else if (value instanceof Double) { - Double doubleValue = (Double) value; - prim = double2Prim(doubleValue); - resolvedType = DOUBLE; - } else { - prim = 0; - resolvedType = OBJECT; - } + public final int getInt(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0 : entry.intValue(); + } - this._setPrim(resolvedType, prim); + public final long getLong(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0L : entry.longValue(); + } - return resolvedType; - } + public final float getFloat(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0F : entry.floatValue(); + } - private void _setPrim(byte type, long prim) { - // Order is important here, the contract is that prim must be set properly *before* - // type is set to a non-object type + public final double getDouble(String tag) { + Entry entry = this.getEntry(tag); + return entry == null ? 0D : entry.doubleValue(); + } - this.rawPrim = prim; - this.rawType = type; - } + @Override + public boolean containsKey(Object key) { + if (!(key instanceof String)) return false; - public final boolean isObject() { - return this.is(OBJECT); - } + return (this.getEntry((String) key) != null); + } - public final boolean isRemoval() { - return false; + @Override + public boolean containsValue(Object value) { + // This could be optimized - but probably isn't called enough to be worth it + for (Entry entry : this) { + if (entry.objectValue().equals(value)) return true; } + return false; + } - public final Object objectValue() { - if (this.rawObj != null) { - return this.rawObj; - } - - // This code doesn't need to handle ANY-s. - // An entry that starts as an ANY will always have this.obj set - switch (this.rawType) { - case BOOLEAN: - this.rawObj = prim2Boolean(this.rawPrim); - break; + @Override + public Set keySet() { + return new Keys(this); + } + + @Override + public Collection values() { + return new Values(this); + } + + @Override + public Set> entrySet() { + return new Entries(this); + } - case INT: - // Maybe use a wider cache that handles response code??? - this.rawObj = prim2Int(this.rawPrim); - break; + @Override + public final Entry getEntry(String tag) { + Object[] thisBuckets = this.buckets; - case LONG: - this.rawObj = prim2Long(this.rawPrim); - break; + int hash = TagMap.Entry._hash(tag); + int bucketIndex = hash & (thisBuckets.length - 1); - case FLOAT: - this.rawObj = prim2Float(this.rawPrim); - break; + Object bucket = thisBuckets[bucketIndex]; + if (bucket == null) { + return null; + } else if (bucket instanceof Entry) { + Entry tagEntry = (Entry) bucket; + if (tagEntry.matches(tag)) return tagEntry; + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; - case DOUBLE: - this.rawObj = prim2Double(this.rawPrim); - break; + Entry tagEntry = lastGroup.findInChain(hash, tag); + if (tagEntry != null) return tagEntry; + } + return null; + } - default: - // DQH - satisfy spot bugs - break; - } + @Deprecated + @Override + public final Object put(String tag, Object value) { + TagMap.Entry entry = this.getAndSet(Entry.newAnyEntry(tag, value)); + return entry == null ? null : entry.objectValue(); + } + + @Override + public final void set(TagMap.Entry newEntry) { + this.getAndSet(newEntry); + } + + @Override + public final void set(String tag, Object value) { + this.getAndSet(Entry.newAnyEntry(tag, value)); + } + + @Override + public final void set(String tag, CharSequence value) { + this.getAndSet(Entry.newObjectEntry(tag, value)); + } - return this.rawObj; - } + @Override + public final void set(String tag, boolean value) { + this.getAndSet(Entry.newBooleanEntry(tag, value)); + } - public final boolean booleanValue() { - byte type = this.rawType; + @Override + public final void set(String tag, int value) { + this.getAndSet(Entry.newIntEntry(tag, value)); + } - if (type == BOOLEAN) { - return prim2Boolean(this.rawPrim); - } else if (type == ANY && this.rawObj instanceof Boolean) { - boolean boolValue = (Boolean) this.rawObj; - this._setPrim(BOOLEAN, boolean2Prim(boolValue)); - return boolValue; - } + @Override + public final void set(String tag, long value) { + this.getAndSet(Entry.newLongEntry(tag, value)); + } - // resolution will set prim if necessary - byte resolvedType = this.resolveAny(); - long prim = this.rawPrim; + @Override + public final void set(String tag, float value) { + this.getAndSet(Entry.newFloatEntry(tag, value)); + } - switch (resolvedType) { - case INT: - return prim2Int(prim) != 0; + @Override + public final void set(String tag, double value) { + this.getAndSet(Entry.newDoubleEntry(tag, value)); + } + + @Override + public final Entry getAndSet(Entry newEntry) { + this.checkWriteAccess(); - case LONG: - return prim2Long(prim) != 0L; + Object[] thisBuckets = this.buckets; - case FLOAT: - return prim2Float(prim) != 0F; + int newHash = newEntry.hash(); + int bucketIndex = newHash & (thisBuckets.length - 1); - case DOUBLE: - return prim2Double(prim) != 0D; + Object bucket = thisBuckets[bucketIndex]; + if (bucket == null) { + thisBuckets[bucketIndex] = newEntry; - case OBJECT: - return (this.rawObj != null); - } + this.size += 1; + return null; + } else if (bucket instanceof Entry) { + Entry existingEntry = (Entry) bucket; + if (existingEntry.matches(newEntry.tag)) { + thisBuckets[bucketIndex] = newEntry; - return false; - } + // replaced existing entry - no size change + return existingEntry; + } else { + thisBuckets[bucketIndex] = + new BucketGroup(existingEntry.hash(), existingEntry, newHash, newEntry); - public final int intValue() { - byte type = this.rawType; + this.size += 1; + return null; + } + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; - if (type == INT) { - return prim2Int(this.rawPrim); - } else if (type == ANY && this.rawObj instanceof Integer) { - int intValue = (Integer) this.rawObj; - this._setPrim(INT, int2Prim(intValue)); - return intValue; + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(newHash, newEntry.tag); + if (containingGroup != null) { + // replaced existing entry - no size change + return containingGroup._replace(newHash, newEntry); } - // resolution will set prim if necessary - byte resolvedType = this.resolveAny(); - long prim = this.rawPrim; + if (!lastGroup.insertInChain(newHash, newEntry)) { + thisBuckets[bucketIndex] = new BucketGroup(newHash, newEntry, lastGroup); + } + this.size += 1; + return null; + } - switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1 : 0; + // unreachable + return null; + } + + @Override + public Entry getAndSet(String tag, Object value) { + return this.getAndSet(Entry.newAnyEntry(tag, value)); + } + + @Override + public Entry getAndSet(String tag, CharSequence value) { + return this.getAndSet(Entry.newObjectEntry(tag, value)); + } + + @Override + public final TagMap.Entry getAndSet(String tag, boolean value) { + return this.getAndSet(Entry.newBooleanEntry(tag, value)); + } - case LONG: - return (int) prim2Long(prim); + @Override + public final TagMap.Entry getAndSet(String tag, int value) { + return this.getAndSet(Entry.newIntEntry(tag, value)); + } - case FLOAT: - return (int) prim2Float(prim); + @Override + public final TagMap.Entry getAndSet(String tag, long value) { + return this.getAndSet(Entry.newLongEntry(tag, value)); + } - case DOUBLE: - return (int) prim2Double(prim); + @Override + public final TagMap.Entry getAndSet(String tag, float value) { + return this.getAndSet(Entry.newFloatEntry(tag, value)); + } - case OBJECT: - return 0; - } + @Override + public final TagMap.Entry getAndSet(String tag, double value) { + return this.getAndSet(Entry.newDoubleEntry(tag, value)); + } + + public final void putAll(Map map) { + this.checkWriteAccess(); - return 0; + if (map instanceof OptimizedTagMap) { + this.putAllOptimizedMap((OptimizedTagMap) map); + } else { + this.putAllUnoptimizedMap(map); + } + } + + private final void putAllUnoptimizedMap(Map that) { + for (Map.Entry entry : that.entrySet()) { + // use set which returns a prior Entry rather put which may box a prior primitive value + this.set(entry.getKey(), entry.getValue()); } + } - public final long longValue() { - byte type = this.rawType; + /** + * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from one TagMap to another + * + *

        For optimized TagMaps, this method takes advantage of the consistent TagMap layout to + * quickly handle each bucket. And similar to {@link TagMap#putEntry(Entry)} this method shares + * Entry objects from the source TagMap + */ + public final void putAll(TagMap that) { + this.checkWriteAccess(); + + if (that instanceof OptimizedTagMap) { + this.putAllOptimizedMap((OptimizedTagMap) that); + } else { + this.putAllUnoptimizedMap(that); + } + } + + private final void putAllOptimizedMap(OptimizedTagMap that) { + if (this.size == 0) { + this.putAllIntoEmptyMap(that); + } else { + this.putAllMerge(that); + } + } - if (type == LONG) { - return prim2Long(this.rawPrim); - } else if (type == ANY && this.rawObj instanceof Long) { - long longValue = (Long) this.rawObj; - this._setPrim(LONG, long2Prim(longValue)); - return longValue; - } + private final void putAllMerge(OptimizedTagMap that) { + Object[] thisBuckets = this.buckets; + Object[] thatBuckets = that.buckets; - // resolution will set prim if necessary - byte resolvedType = this.resolveAny(); - long prim = this.rawPrim; + // Since TagMap-s don't support expansion, buckets are perfectly aligned + // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check + // elimination + for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { + Object thatBucket = thatBuckets[i]; - switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1L : 0L; + // if nothing incoming, nothing to do + if (thatBucket == null) continue; - case INT: - return (long) prim2Int(prim); + Object thisBucket = thisBuckets[i]; + if (thisBucket == null) { + // This bucket is null, easy case + // Either copy over the sole entry or clone the BucketGroup chain - case FLOAT: - return (long) prim2Float(prim); + if (thatBucket instanceof Entry) { + thisBuckets[i] = thatBucket; + this.size += 1; + } else if (thatBucket instanceof BucketGroup) { + BucketGroup thatGroup = (BucketGroup) thatBucket; - case DOUBLE: - return (long) prim2Double(prim); + BucketGroup thisNewGroup = thatGroup.cloneChain(); + thisBuckets[i] = thisNewGroup; + this.size += thisNewGroup.sizeInChain(); + } + } else if (thisBucket instanceof Entry) { + // This bucket is a single entry, medium complexity case + // If other side is an Entry - just merge the entries into a bucket + // If other side is a BucketGroup - then clone the group and insert the entry normally into + // the cloned group - case OBJECT: - return 0; - } + Entry thisEntry = (Entry) thisBucket; + int thisHash = thisEntry.hash(); - return 0; - } + if (thatBucket instanceof Entry) { + Entry thatEntry = (Entry) thatBucket; + int thatHash = thatEntry.hash(); - public final float floatValue() { - byte type = this.rawType; + if (thisHash == thatHash && thisEntry.matches(thatEntry.tag())) { + thisBuckets[i] = thatEntry; + // replacing entry, no size change + } else { + thisBuckets[i] = + new BucketGroup( + thisHash, thisEntry, + thatHash, thatEntry); + this.size += 1; + } + } else if (thatBucket instanceof BucketGroup) { + BucketGroup thatGroup = (BucketGroup) thatBucket; - if (type == FLOAT) { - return prim2Float(this.rawPrim); - } else if (type == ANY && this.rawObj instanceof Float) { - float floatValue = (Float) this.rawObj; - this._setPrim(FLOAT, float2Prim(floatValue)); - return floatValue; - } + // Clone the other group, then place this entry into that group + BucketGroup thisNewGroup = thatGroup.cloneChain(); + int thisNewGroupSize = thisNewGroup.sizeInChain(); - // resolution will set prim if necessary - byte resolvedType = this.resolveAny(); - long prim = this.rawPrim; + Entry incomingEntry = thisNewGroup.findInChain(thisHash, thisEntry.tag()); + if (incomingEntry != null) { + // there's already an entry w/ the same tag from the incoming TagMap + // incoming entry clobbers the existing try, so we're done + thisBuckets[i] = thisNewGroup; - switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1F : 0F; + // overlapping group - subtract one for clobbered existing entry + this.size += thisNewGroupSize - 1; + } else if (thisNewGroup.insertInChain(thisHash, thisEntry)) { + // able to add thisEntry into the existing groups + thisBuckets[i] = thisNewGroup; - case INT: - return (float) prim2Int(prim); + // non overlapping group - existing entry already accounted for in this.size + this.size += thisNewGroupSize; + } else { + // unable to add into the existing groups + thisBuckets[i] = new BucketGroup(thisHash, thisEntry, thisNewGroup); - case LONG: - return (float) prim2Long(prim); + // non overlapping group - existing entry already accounted for in this.size + this.size += thisNewGroupSize; + } + } + } else if (thisBucket instanceof BucketGroup) { + // This bucket is a BucketGroup, medium to hard case + // If the other side is an entry, just normal insertion procedure - no cloning required + BucketGroup thisGroup = (BucketGroup) thisBucket; - case DOUBLE: - return (float) prim2Double(prim); + if (thatBucket instanceof Entry) { + Entry thatEntry = (Entry) thatBucket; + int thatHash = thatEntry.hash(); - case OBJECT: - return 0F; - } + if (thisGroup.replaceInChain(thatHash, thatEntry) != null) { + // replaced existing entry no size change + } else if (thisGroup.insertInChain(thatHash, thatEntry)) { + this.size += 1; + } else { + thisBuckets[i] = new BucketGroup(thatHash, thatEntry, thisGroup); + this.size += 1; + } + } else if (thatBucket instanceof BucketGroup) { + // Most complicated case - need to walk that bucket group chain and update this chain + BucketGroup thatGroup = (BucketGroup) thatBucket; - return 0F; - } + // Taking the easy / expensive way out for updating size + int thisPrevGroupSize = thisGroup.sizeInChain(); - public final double doubleValue() { - byte type = this.rawType; + BucketGroup thisNewGroup = thisGroup.replaceOrInsertAllInChain(thatGroup); + int thisNewGroupSize = thisNewGroup.sizeInChain(); - if (type == DOUBLE) { - return prim2Double(this.rawPrim); - } else if (type == ANY && this.rawObj instanceof Double) { - double doubleValue = (Double) this.rawObj; - this._setPrim(DOUBLE, double2Prim(doubleValue)); - return doubleValue; + thisBuckets[i] = thisNewGroup; + this.size += (thisNewGroupSize - thisPrevGroupSize); + } } + } + } - // resolution will set prim if necessary - byte resolvedType = this.resolveAny(); - long prim = this.rawPrim; - - switch (resolvedType) { - case BOOLEAN: - return prim2Boolean(prim) ? 1D : 0D; - - case INT: - return (double) prim2Int(prim); + /* + * Specially optimized version of putAll for the common case of destination map being empty + */ + private final void putAllIntoEmptyMap(OptimizedTagMap that) { + Object[] thisBuckets = this.buckets; + Object[] thatBuckets = that.buckets; - case LONG: - return (double) prim2Long(prim); + // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check + // elimination + for (int i = 0; i < thisBuckets.length && i < thatBuckets.length; ++i) { + Object thatBucket = thatBuckets[i]; - case FLOAT: - return (double) prim2Float(prim); + // faster to explicitly null check first, then do instanceof + if (thatBucket == null) { + // do nothing + } else if (thatBucket instanceof BucketGroup) { + // if it is a BucketGroup, then need to clone + BucketGroup thatGroup = (BucketGroup) thatBucket; - case OBJECT: - return 0D; + thisBuckets[i] = thatGroup.cloneChain(); + } else { // if ( thatBucket instanceof Entry ) + thisBuckets[i] = thatBucket; } - - return 0D; } + this.size = that.size; + } - public final String stringValue() { - String strCache = this.strCache; - if (strCache != null) { - return strCache; - } + public final void fillMap(Map map) { + Object[] thisBuckets = this.buckets; - String computeStr = this.computeStringValue(); - this.strCache = computeStr; - return computeStr; - } + for (int i = 0; i < thisBuckets.length; ++i) { + Object thisBucket = thisBuckets[i]; - private final String computeStringValue() { - // Could do type resolution here, - // but decided to just fallback to this.obj.toString() for ANY case - switch (this.rawType) { - case BOOLEAN: - return Boolean.toString(prim2Boolean(this.rawPrim)); + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + + map.put(thisEntry.tag, thisEntry.objectValue()); + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; - case INT: - return Integer.toString(prim2Int(this.rawPrim)); + thisGroup.fillMapFromChain(map); + } + } + } - case LONG: - return Long.toString(prim2Long(this.rawPrim)); + public final void fillStringMap(Map stringMap) { + Object[] thisBuckets = this.buckets; - case FLOAT: - return Float.toString(prim2Float(this.rawPrim)); + for (int i = 0; i < thisBuckets.length; ++i) { + Object thisBucket = thisBuckets[i]; - case DOUBLE: - return Double.toString(prim2Double(this.rawPrim)); + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; - case OBJECT: - case ANY: - return this.rawObj.toString(); - } + stringMap.put(thisEntry.tag, thisEntry.stringValue()); + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; - return null; + thisGroup.fillStringMapFromChain(stringMap); + } } + } - @Override - public final String toString() { - return this.tag() + '=' + this.stringValue(); - } + @Override + public final Object remove(Object tag) { + if (!(tag instanceof String)) return null; - @Deprecated - @Override - public String getKey() { - return this.tag(); - } + Entry entry = this.getAndRemove((String) tag); + return entry == null ? null : entry.objectValue(); + } + + public final boolean remove(String tag) { + return (this.getAndRemove(tag) != null); + } + + @Override + public final Entry getAndRemove(String tag) { + this.checkWriteAccess(); - @Deprecated - @Override - public Object getValue() { - return this.objectValue(); - } + Object[] thisBuckets = this.buckets; - @Deprecated - @Override - public Object setValue(Object value) { - throw new UnsupportedOperationException(); - } + int hash = TagMap.Entry._hash(tag); + int bucketIndex = hash & (thisBuckets.length - 1); - private static final long boolean2Prim(boolean value) { - return value ? 1L : 0L; - } + Object bucket = thisBuckets[bucketIndex]; + // null bucket case - do nothing + if (bucket instanceof Entry) { + Entry existingEntry = (Entry) bucket; + if (existingEntry.matches(tag)) { + thisBuckets[bucketIndex] = null; - private static final boolean prim2Boolean(long prim) { - return (prim != 0L); - } + this.size -= 1; + return existingEntry; + } else { + return null; + } + } else if (bucket instanceof BucketGroup) { + BucketGroup lastGroup = (BucketGroup) bucket; - private static final long int2Prim(int value) { - return (long) value; - } + BucketGroup containingGroup = lastGroup.findContainingGroupInChain(hash, tag); + if (containingGroup == null) { + return null; + } - private static final int prim2Int(long prim) { - return (int) prim; - } + Entry existingEntry = containingGroup._remove(hash, tag); + if (containingGroup._isEmpty()) { + this.buckets[bucketIndex] = lastGroup.removeGroupInChain(containingGroup); + } - private static final long long2Prim(long value) { - return value; + this.size -= 1; + return existingEntry; } - - private static final long prim2Long(long prim) { - return prim; + return null; + } + + @Override + public final TagMap copy() { + OptimizedTagMap copy = new OptimizedTagMap(); + copy.putAllIntoEmptyMap(this); + return copy; + } + + public final TagMap immutableCopy() { + if (this.frozen) { + return this; + } else { + return this.copy().freeze(); } + } + + @Override + public final Iterator iterator() { + return new EntryIterator(this); + } - private static final long float2Prim(float value) { - return (long) Float.floatToIntBits(value); - } + @Override + public final Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + @Override + public final void forEach(Consumer consumer) { + Object[] thisBuckets = this.buckets; - private static final float prim2Float(long prim) { - return Float.intBitsToFloat((int) prim); - } + for (int i = 0; i < thisBuckets.length; ++i) { + Object thisBucket = thisBuckets[i]; - private static final long double2Prim(double value) { - return Double.doubleToRawLongBits(value); - } + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; - private static final double prim2Double(long prim) { - return Double.longBitsToDouble(prim); + consumer.accept(thisEntry); + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; + + thisGroup.forEachInChain(consumer); + } } } - /* - * An in-order ledger of changes to be made to a TagMap. - * Ledger can also serves as a builder for TagMap-s via build & buildImmutable. - */ - public static final class Ledger implements Iterable { - private EntryChange[] entryChanges; - private int nextPos = 0; - private boolean containsRemovals = false; + @Override + public final void forEach(T thisObj, BiConsumer consumer) { + Object[] thisBuckets = this.buckets; - private Ledger() { - this(8); - } + for (int i = 0; i < thisBuckets.length; ++i) { + Object thisBucket = thisBuckets[i]; - private Ledger(int size) { - this.entryChanges = new EntryChange[size]; - } + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; - public final boolean isDefinitelyEmpty() { - return (this.nextPos == 0); - } + consumer.accept(thisObj, thisEntry); + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; - /** - * Provides the estimated size of the map created by the ledger Doesn't account for overwritten - * entries or entry removal - * - * @return - */ - public final int estimateSize() { - return this.nextPos; + thisGroup.forEachInChain(thisObj, consumer); + } } + } + + @Override + public final void forEach( + T thisObj, U otherObj, TriConsumer consumer) { + Object[] thisBuckets = this.buckets; - public final boolean containsRemovals() { - return this.containsRemovals; - } + for (int i = 0; i < thisBuckets.length; ++i) { + Object thisBucket = thisBuckets[i]; - public final Ledger set(String tag, Object value) { - return this.recordEntry(Entry.newAnyEntry(tag, value)); - } + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; - public final Ledger set(String tag, CharSequence value) { - return this.recordEntry(Entry.newObjectEntry(tag, value)); - } + consumer.accept(thisObj, otherObj, thisEntry); + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; - public final Ledger set(String tag, boolean value) { - return this.recordEntry(Entry.newBooleanEntry(tag, value)); + thisGroup.forEachInChain(thisObj, otherObj, consumer); + } } + } + + public final void clear() { + this.checkWriteAccess(); - public final Ledger set(String tag, int value) { - return this.recordEntry(Entry.newIntEntry(tag, value)); - } + Arrays.fill(this.buckets, null); + this.size = 0; + } - public final Ledger set(String tag, long value) { - return this.recordEntry(Entry.newLongEntry(tag, value)); - } + public final OptimizedTagMap freeze() { + this.frozen = true; - public final Ledger set(String tag, float value) { - return this.recordEntry(Entry.newFloatEntry(tag, value)); - } + return this; + } - public final Ledger set(String tag, double value) { - return this.recordEntry(Entry.newDoubleEntry(tag, value)); - } + public boolean isFrozen() { + return this.frozen; + } - public final Ledger set(Entry entry) { - return this.recordEntry(entry); - } + public final void checkWriteAccess() { + if (this.frozen) throw new IllegalStateException("TagMap frozen"); + } - public final Ledger remove(String tag) { - return this.recordRemoval(EntryChange.newRemoval(tag)); - } + final void checkIntegrity() { + // Decided to use if ( cond ) throw new IllegalStateException rather than assert + // That was done to avoid the extra static initialization needed for an assertion + // While that's probably an unnecessary optimization, this method is only called in tests - private final Ledger recordEntry(Entry entry) { - this.recordChange(entry); - return this; - } + Object[] thisBuckets = this.buckets; - private final Ledger recordRemoval(EntryRemoval entry) { - this.recordChange(entry); - this.containsRemovals = true; + for (int i = 0; i < thisBuckets.length; ++i) { + Object thisBucket = thisBuckets[i]; - return this; - } + if (thisBucket instanceof Entry) { + Entry thisEntry = (Entry) thisBucket; + int thisHash = thisEntry.hash(); + + int expectedBucket = thisHash & (thisBuckets.length - 1); + if (expectedBucket != i) { + throw new IllegalStateException("incorrect bucket"); + } + } else if (thisBucket instanceof BucketGroup) { + BucketGroup thisGroup = (BucketGroup) thisBucket; - private final void recordChange(EntryChange entryChange) { - if (this.nextPos >= this.entryChanges.length) { - this.entryChanges = Arrays.copyOf(this.entryChanges, this.entryChanges.length << 1); - } + for (BucketGroup curGroup = thisGroup; curGroup != null; curGroup = curGroup.prev) { + for (int j = 0; j < BucketGroup.LEN; ++j) { + Entry thisEntry = curGroup._entryAt(i); + if (thisEntry == null) continue; - this.entryChanges[this.nextPos++] = entryChange; - } + int thisHash = thisEntry.hash(); + assert curGroup._hashAt(i) == thisHash; - public final Ledger smartRemove(String tag) { - if (this.contains(tag)) { - this.remove(tag); + int expectedBucket = thisHash & (thisBuckets.length - 1); + if (expectedBucket != i) { + throw new IllegalStateException("incorrect bucket"); + } + } + } } - return this; } - private final boolean contains(String tag) { - EntryChange[] thisChanges = this.entryChanges; - - // min is to clamp, so bounds check elimination optimization works - int lenClamp = Math.min(this.nextPos, thisChanges.length); - for (int i = 0; i < lenClamp; ++i) { - if (thisChanges[i].matches(tag)) return true; - } - return false; + if (this.size != this.computeSize()) { + throw new IllegalStateException("incorrect size"); + } + if (this.isEmpty() != this.checkIfEmpty()) { + throw new IllegalStateException("incorrect empty status"); } + } - /* - * Just for testing - */ - final Entry findLastEntry(String tag) { - EntryChange[] thisChanges = this.entryChanges; + final int computeSize() { + Object[] thisBuckets = this.buckets; - // min is to clamp, so ArrayBoundsCheckElimination optimization works - int clampLen = Math.min(this.nextPos, thisChanges.length) - 1; - for (int i = clampLen; i >= 0; --i) { - EntryChange thisChange = thisChanges[i]; - if (!thisChange.isRemoval() && thisChange.matches(tag)) return (Entry) thisChange; - } - return null; - } + int size = 0; + for (int i = 0; i < thisBuckets.length; ++i) { + Object curBucket = thisBuckets[i]; - public final void reset() { - Arrays.fill(this.entryChanges, null); - this.nextPos = 0; + if (curBucket instanceof Entry) { + size += 1; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; + size += curGroup.sizeInChain(); + } } + return size; + } - @Override - public final Iterator iterator() { - return new BuilderIterator(this.entryChanges, this.nextPos); - } + final boolean checkIfEmpty() { + Object[] thisBuckets = this.buckets; - public TagMap build() { - TagMap map = new TagMap(); - if (this.nextPos != 0) map.putAll(this.entryChanges, this.nextPos); - return map; - } + for (int i = 0; i < thisBuckets.length; ++i) { + Object curBucket = thisBuckets[i]; - public TagMap buildImmutable() { - if (this.nextPos == 0) { - return TagMap.EMPTY; - } else { - return this.build().freeze(); + if (curBucket instanceof Entry) { + return false; + } else if (curBucket instanceof BucketGroup) { + BucketGroup curGroup = (BucketGroup) curBucket; + if (!curGroup.isEmptyChain()) return false; } } - } - private static final class BuilderIterator implements Iterator { - private final EntryChange[] entryChanges; - private final int size; + return true; + } + + @Override + public Object compute( + String key, BiFunction remappingFunction) + { + this.checkWriteAccess(); + + return TagMap.super.compute(key, remappingFunction); + } + + @Override + public Object computeIfAbsent(String key, Function mappingFunction) { + this.checkWriteAccess(); + + return TagMap.super.computeIfAbsent(key, mappingFunction); + } + + @Override + public Object computeIfPresent(String key, + BiFunction remappingFunction) + { + this.checkWriteAccess(); + + return TagMap.super.computeIfPresent(key, remappingFunction); + } + + @Override + public final String toString() { + return toPrettyString(); + } - private int pos; + /** + * Standard toString implementation - output is similar to {@link java.util.HashMap#toString()} + */ + final String toPrettyString() { + boolean first = true; - BuilderIterator(EntryChange[] entryChanges, int size) { - this.entryChanges = entryChanges; - this.size = size; + StringBuilder ledger = new StringBuilder(128); + ledger.append('{'); + for (Entry entry : this) { + if (first) { + first = false; + } else { + ledger.append(", "); + } - this.pos = -1; + ledger.append(entry.tag).append('=').append(entry.stringValue()); } + ledger.append('}'); + return ledger.toString(); + } - @Override - public final boolean hasNext() { - return (this.pos + 1 < this.size); - } + /** + * toString that more visibility into the internal structure of TagMap - primarily for deep + * debugging + */ + final String toInternalString() { + Object[] thisBuckets = this.buckets; - @Override - public EntryChange next() { - if (!this.hasNext()) throw new NoSuchElementException("no next"); + StringBuilder ledger = new StringBuilder(128); + for (int i = 0; i < thisBuckets.length; ++i) { + ledger.append('[').append(i).append("] = "); - return this.entryChanges[++this.pos]; + Object thisBucket = thisBuckets[i]; + if (thisBucket == null) { + ledger.append("null"); + } else if (thisBucket instanceof Entry) { + ledger.append('{').append(thisBucket).append('}'); + } else if (thisBucket instanceof BucketGroup) { + for (BucketGroup curGroup = (BucketGroup) thisBucket; + curGroup != null; + curGroup = curGroup.prev) { + ledger.append(curGroup).append(" -> "); + } + } + ledger.append('\n'); } + return ledger.toString(); } + - private abstract static class MapIterator implements Iterator { + abstract static class MapIterator implements Iterator { private final Object[] buckets; private Entry nextEntry; @@ -1624,7 +1920,7 @@ private abstract static class MapIterator implements Iterator { private BucketGroup group = null; private int groupIndex = 0; - MapIterator(TagMap map) { + MapIterator(OptimizedTagMap map) { this.buckets = map.buckets; } @@ -1690,7 +1986,7 @@ private final Entry advance() { } static final class EntryIterator extends MapIterator { - EntryIterator(TagMap map) { + EntryIterator(OptimizedTagMap map) { super(map); } @@ -2163,10 +2459,10 @@ public String toString() { } } - private static final class Entries extends AbstractSet> { - private final TagMap map; + static final class Entries extends AbstractSet> { + private final OptimizedTagMap map; - Entries(TagMap map) { + Entries(OptimizedTagMap map) { this.map = map; } @@ -2188,10 +2484,10 @@ public Iterator> iterator() { } } - private static final class Keys extends AbstractSet { - private final TagMap map; + static final class Keys extends AbstractSet { + final OptimizedTagMap map; - Keys(TagMap map) { + Keys(OptimizedTagMap map) { this.map = map; } @@ -2217,7 +2513,7 @@ public Iterator iterator() { } static final class KeysIterator extends MapIterator { - KeysIterator(TagMap map) { + KeysIterator(OptimizedTagMap map) { super(map); } @@ -2227,10 +2523,10 @@ public String next() { } } - private static final class Values extends AbstractCollection { - private final TagMap map; + static final class Values extends AbstractCollection { + final OptimizedTagMap map; - Values(TagMap map) { + Values(OptimizedTagMap map) { this.map = map; } @@ -2256,7 +2552,7 @@ public Iterator iterator() { } static final class ValuesIterator extends MapIterator { - ValuesIterator(TagMap map) { + ValuesIterator(OptimizedTagMap map) { super(map); } @@ -2266,3 +2562,365 @@ public Object next() { } } } + +final class LegacyTagMap extends HashMap implements TagMap { + private static final long serialVersionUID = 77473435283123683L; + + static final LegacyTagMap EMPTY = new LegacyTagMap().freeze(); + + private boolean frozen = false; + + LegacyTagMap() { + super(); + } + + LegacyTagMap(int capacity) { + super(capacity); + } + + LegacyTagMap(LegacyTagMap that) { + super(that); + } + + @Override + public boolean isOptimized() { + return false; + } + + @Override + public void clear() { + this.checkWriteAccess(); + + super.clear(); + } + + public final LegacyTagMap freeze() { + this.frozen = true; + + return this; + } + + public boolean isFrozen() { + return this.frozen; + } + + public final void checkWriteAccess() { + if (this.frozen) throw new IllegalStateException("TagMap frozen"); + } + + @Override + public final TagMap copy() { + return new LegacyTagMap(this); + } + + @Override + public final void fillMap(Map map) { + map.putAll(this); + } + + @Override + public final void fillStringMap(Map stringMap) { + for ( Map.Entry entry: this.entrySet() ) { + stringMap.put(entry.getKey(), entry.getValue().toString()); + } + } + + @Override + public final void forEach(Consumer consumer) { + for ( Map.Entry entry: this.entrySet() ) { + consumer.accept(TagMap.Entry.newAnyEntry(entry)); + } + } + + @Override + public final void forEach(T thisObj, BiConsumer consumer) { + for ( Map.Entry entry: this.entrySet() ) { + consumer.accept(thisObj, TagMap.Entry.newAnyEntry(entry)); + } + } + + @Override + public final void forEach(T thisObj, U otherObj, + TriConsumer consumer) + { + for ( Map.Entry entry: this.entrySet() ) { + consumer.accept(thisObj, otherObj, TagMap.Entry.newAnyEntry(entry)); + } + } + + @Override + public final Object getObject(String tag) { + return this.get(tag); + } + + @Override + public final boolean getBoolean(String tag) { + Object result = this.get(tag); + if ( result == null ) return false; + + if ( result instanceof Boolean ) { + return (Boolean)result; + } else if ( result instanceof Number ) { + Number number = (Number)result; + return (number.intValue() != 0); + } else { + return true; + } + } + + @Override + public final TagMap.Entry getAndSet(String tag, Object value) { + Object prior = this.put(tag, value); + return prior == null ? null : TagMap.Entry.newAnyEntry(tag, prior); + } + + @Override + public final TagMap.Entry getAndSet(String tag, CharSequence value) { + Object prior = this.put(tag, value); + return prior == null ? null : TagMap.Entry.newAnyEntry(tag, prior); + } + + @Override + public final TagMap.Entry getAndSet(String tag, boolean value) { + return this.getAndSet(tag, Boolean.valueOf(value)); + } + + @Override + public final TagMap.Entry getAndSet(String tag, double value) { + return this.getAndSet(tag, Double.valueOf(value)); + } + + @Override + public final TagMap.Entry getAndSet(String tag, float value) { + return this.getAndSet(tag, Float.valueOf(value)); + } + + @Override + public final TagMap.Entry getAndSet(String tag, int value) { + return this.getAndSet(tag, Integer.valueOf(value)); + } + + @Override + public final TagMap.Entry getAndSet(String tag, long value) { + return this.getAndSet(tag, Long.valueOf(value)); + } + + @Override + public final TagMap.Entry getAndSet(TagMap.Entry newEntry) { + return this.getAndSet(newEntry.tag(), newEntry.objectValue()); + } + + @Override + public final TagMap.Entry getAndRemove(String tag) { + Object prior = this.remove((Object)tag); + return prior == null ? null : TagMap.Entry.newAnyEntry(tag, prior); + } + + @Override + public final double getDouble(String tag) { + Object value = this.get(tag); + if ( value instanceof Number ) { + return ((Number)value).doubleValue(); + } else if ( value instanceof Boolean ) { + return ((Boolean)value) ? 1D : 0D; + } else { + return 0D; + } + } + + @Override + public final long getLong(String tag) { + Object value = this.get(tag); + if ( value == null ) { + return 0L; + } else if ( value instanceof Number ) { + return ((Number)value).longValue(); + } else if ( value instanceof Boolean ) { + return ((Boolean)value) ? 1L : 0L; + } else { + return 0L; + } + } + + @Override + public final float getFloat(String tag) { + Object value = this.get(tag); + if ( value == null ) { + return 0F; + } else if ( value instanceof Number ) { + return ((Number)value).floatValue(); + } else if ( value instanceof Boolean ) { + return ((Boolean)value) ? 1F : 0F; + } else { + return 0F; + } + } + + @Override + public final int getInt(String tag) { + Object value = this.get(tag); + if ( value == null ) { + return 0; + } else if ( value instanceof Number ) { + return ((Number)value).intValue(); + } else if ( value instanceof Boolean ) { + return ((Boolean)value) ? 1 : 0; + } else { + return 0; + } + } + + @Override + public final String getString(String tag) { + Object value = this.get(tag); + return value == null ? null : value.toString(); + } + + @Override + public final TagMap.Entry getEntry(String tag) { + Object value = this.get(tag); + return value == null ? null : TagMap.Entry.newAnyEntry(tag, value); + } + + @Override + public void set(String tag, boolean value) { + this.put(tag, Boolean.valueOf(value)); + } + + @Override + public void set(String tag, CharSequence value) { + this.put(tag, (Object)value); + } + + @Override + public void set(String tag, double value) { + this.put(tag, Double.valueOf(value)); + } + + @Override + public void set(String tag, float value) { + this.put(tag, Float.valueOf(value)); + } + + @Override + public void set(String tag, int value) { + this.put(tag, Integer.valueOf(value)); + } + + @Override + public void set(String tag, long value) { + this.put(tag, Long.valueOf(value)); + } + + @Override + public void set(String tag, Object value) { + this.put(tag, value); + } + + @Override + public void set(TagMap.Entry newEntry) { + this.put(newEntry.tag(), newEntry.objectValue()); + } + + @Override + public Object put(String key, Object value) { + this.checkWriteAccess(); + + return super.put(key, value); + } + + @Override + public void putAll(Map m) { + this.checkWriteAccess(); + + super.putAll(m); + } + + @Override + public void putAll(TagMap that) { + this.putAll((Map)that); + } + + @Override + public Object remove(Object key) { + this.checkWriteAccess(); + + return super.remove(key); + } + + @Override + public boolean remove(Object key, Object value) { + this.checkWriteAccess(); + + return super.remove(key, value); + } + + @Override + public boolean remove(String tag) { + this.checkWriteAccess(); + + return (super.remove((Object)tag) != null); + } + + @Override + public Object compute( + String key, BiFunction remappingFunction) + { + this.checkWriteAccess(); + + return super.compute(key, remappingFunction); + } + + @Override + public Object computeIfAbsent(String key, Function mappingFunction) { + this.checkWriteAccess(); + + return super.computeIfAbsent(key, mappingFunction); + } + + @Override + public Object computeIfPresent(String key, + BiFunction remappingFunction) + { + this.checkWriteAccess(); + + return super.computeIfPresent(key, remappingFunction); + } + + @Override + public TagMap immutableCopy() { + if ( this.isEmpty() ) { + return LegacyTagMap.EMPTY; + } else { + return this.copy().freeze(); + } + } + + @Override + public Iterator iterator() { + return new IteratorImpl(this); + } + + @Override + public Stream stream() { + return StreamSupport.stream(this.spliterator(), false); + } + + private final class IteratorImpl implements Iterator { + private Iterator> wrappedIter; + + IteratorImpl(LegacyTagMap legacyMap) { + this.wrappedIter = legacyMap.entrySet().iterator(); + } + + @Override + public final boolean hasNext() { + return this.wrappedIter.hasNext(); + } + + @Override + public final TagMap.Entry next() { + return TagMap.Entry.newAnyEntry(this.wrappedIter.next()); + } + } +} diff --git a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java index d833e5760bf..11b06a580d7 100644 --- a/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java +++ b/internal-api/src/main/java/datadog/trace/bootstrap/instrumentation/api/TagContext.java @@ -173,7 +173,7 @@ public final TagMap getTags() { public void putTag(final String key, final String value) { if (this.tags == null) { - this.tags = new TagMap(); + this.tags = TagMap.create(4); } this.tags.set(key, value); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index cad674ffeca..b1f6f983955 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -19,8 +19,8 @@ public void newGroup() { int firstHash = firstEntry.hash(); int secondHash = secondEntry.hash(); - TagMap.BucketGroup group = - new TagMap.BucketGroup( + OptimizedTagMap.BucketGroup group = + new OptimizedTagMap.BucketGroup( firstHash, firstEntry, secondHash, secondEntry); @@ -45,8 +45,8 @@ public void _insert() { int firstHash = firstEntry.hash(); int secondHash = secondEntry.hash(); - TagMap.BucketGroup group = - new TagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); + OptimizedTagMap.BucketGroup group = + new OptimizedTagMap.BucketGroup(firstHash, firstEntry, secondHash, secondEntry); TagMap.Entry newEntry = TagMap.Entry.newAnyEntry("baz", "lorem ipsum"); int newHash = newEntry.hash(); @@ -82,7 +82,7 @@ public void _replace() { int origHash = origEntry.hash(); int otherHash = otherEntry.hash(); - TagMap.BucketGroup group = new TagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); + OptimizedTagMap.BucketGroup group = new OptimizedTagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); assertContainsDirectly(origEntry, group); assertContainsDirectly(otherEntry, group); @@ -111,8 +111,8 @@ public void _remove() { int firstHash = firstEntry.hash(); int secondHash = secondEntry.hash(); - TagMap.BucketGroup group = - new TagMap.BucketGroup( + OptimizedTagMap.BucketGroup group = + new OptimizedTagMap.BucketGroup( firstHash, firstEntry, secondHash, secondEntry); @@ -136,9 +136,9 @@ public void _remove() { @Test public void groupChaining() { int startingIndex = 10; - TagMap.BucketGroup firstGroup = fullGroup(startingIndex); + OptimizedTagMap.BucketGroup firstGroup = fullGroup(startingIndex); - for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + for (int offset = 0; offset < OptimizedTagMap.BucketGroup.LEN; ++offset) { assertChainContainsTag(tag(startingIndex + offset), firstGroup); } @@ -150,88 +150,88 @@ public void groupChaining() { assertFalse(firstGroup._insert(newHash, newEntry)); assertDoesntContainDirectly(newEntry, firstGroup); - TagMap.BucketGroup newHeadGroup = new TagMap.BucketGroup(newHash, newEntry, firstGroup); + OptimizedTagMap.BucketGroup newHeadGroup = new OptimizedTagMap.BucketGroup(newHash, newEntry, firstGroup); assertContainsDirectly(newEntry, newHeadGroup); assertSame(firstGroup, newHeadGroup.prev); assertChainContainsTag("new", newHeadGroup); - for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + for (int offset = 0; offset < OptimizedTagMap.BucketGroup.LEN; ++offset) { assertChainContainsTag(tag(startingIndex + offset), newHeadGroup); } } @Test public void removeInChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + OptimizedTagMap.BucketGroup firstGroup = fullGroup(10); + OptimizedTagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - for (int offset = 0; offset < TagMap.BucketGroup.LEN; ++offset) { + for (int offset = 0; offset < OptimizedTagMap.BucketGroup.LEN; ++offset) { assertChainContainsTag(tag(10, offset), headGroup); assertChainContainsTag(tag(20, offset), headGroup); } - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2); String firstRemovedTag = tag(10, 1); - int firstRemovedHash = TagMap._hash(firstRemovedTag); + int firstRemovedHash = TagMap.Entry._hash(firstRemovedTag); - TagMap.BucketGroup firstContainingGroup = + OptimizedTagMap.BucketGroup firstContainingGroup = headGroup.findContainingGroupInChain(firstRemovedHash, firstRemovedTag); assertSame(firstContainingGroup, firstGroup); assertNotNull(firstContainingGroup._remove(firstRemovedHash, firstRemovedTag)); assertChainDoesntContainTag(firstRemovedTag, headGroup); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 1); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2 - 1); String secondRemovedTag = tag(20, 2); - int secondRemovedHash = TagMap._hash(secondRemovedTag); + int secondRemovedHash = TagMap.Entry._hash(secondRemovedTag); - TagMap.BucketGroup secondContainingGroup = + OptimizedTagMap.BucketGroup secondContainingGroup = headGroup.findContainingGroupInChain(secondRemovedHash, secondRemovedTag); assertSame(secondContainingGroup, headGroup); assertNotNull(secondContainingGroup._remove(secondRemovedHash, secondRemovedTag)); assertChainDoesntContainTag(secondRemovedTag, headGroup); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2 - 2); } @Test public void replaceInChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + OptimizedTagMap.BucketGroup firstGroup = fullGroup(10); + OptimizedTagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2); TagMap.Entry firstReplacementEntry = TagMap.Entry.newObjectEntry(tag(10, 1), "replaced"); assertNotNull(headGroup.replaceInChain(firstReplacementEntry.hash(), firstReplacementEntry)); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2); TagMap.Entry secondReplacementEntry = TagMap.Entry.newObjectEntry(tag(20, 2), "replaced"); assertNotNull(headGroup.replaceInChain(secondReplacementEntry.hash(), secondReplacementEntry)); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2); } @Test public void insertInChain() { // set-up a chain with some gaps in it - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup headGroup = fullGroup(20, firstGroup); + OptimizedTagMap.BucketGroup firstGroup = fullGroup(10); + OptimizedTagMap.BucketGroup headGroup = fullGroup(20, firstGroup); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2); String firstHoleTag = tag(10, 1); - int firstHoleHash = TagMap._hash(firstHoleTag); + int firstHoleHash = TagMap.Entry._hash(firstHoleTag); firstGroup._remove(firstHoleHash, firstHoleTag); String secondHoleTag = tag(20, 2); - int secondHoleHash = TagMap._hash(secondHoleTag); + int secondHoleHash = TagMap.Entry._hash(secondHoleTag); headGroup._remove(secondHoleHash, secondHoleTag); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2 - 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2 - 2); String firstNewTag = "new-tag-0"; TagMap.Entry firstNewEntry = TagMap.Entry.newObjectEntry(firstNewTag, "new"); @@ -254,18 +254,18 @@ public void insertInChain() { assertFalse(headGroup.insertInChain(thirdNewHash, thirdNewEntry)); assertChainDoesntContainTag(thirdNewTag, headGroup); - assertEquals(headGroup.sizeInChain(), TagMap.BucketGroup.LEN * 2); + assertEquals(headGroup.sizeInChain(), OptimizedTagMap.BucketGroup.LEN * 2); } @Test public void cloneChain() { - TagMap.BucketGroup firstGroup = fullGroup(10); - TagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); - TagMap.BucketGroup headGroup = fullGroup(30, secondGroup); + OptimizedTagMap.BucketGroup firstGroup = fullGroup(10); + OptimizedTagMap.BucketGroup secondGroup = fullGroup(20, firstGroup); + OptimizedTagMap.BucketGroup headGroup = fullGroup(30, secondGroup); - TagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); - TagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; - TagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; + OptimizedTagMap.BucketGroup clonedHeadGroup = headGroup.cloneChain(); + OptimizedTagMap.BucketGroup clonedSecondGroup = clonedHeadGroup.prev; + OptimizedTagMap.BucketGroup clonedFirstGroup = clonedSecondGroup.prev; assertGroupContentsStrictEquals(headGroup, clonedHeadGroup); assertGroupContentsStrictEquals(secondGroup, clonedSecondGroup); @@ -274,11 +274,11 @@ public void cloneChain() { @Test public void removeGroupInChain() { - TagMap.BucketGroup tailGroup = fullGroup(10); - TagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); - TagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); - TagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); - TagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); + OptimizedTagMap.BucketGroup tailGroup = fullGroup(10); + OptimizedTagMap.BucketGroup secondGroup = fullGroup(20, tailGroup); + OptimizedTagMap.BucketGroup thirdGroup = fullGroup(30, secondGroup); + OptimizedTagMap.BucketGroup fourthGroup = fullGroup(40, thirdGroup); + OptimizedTagMap.BucketGroup headGroup = fullGroup(50, fourthGroup); assertChain(headGroup, fourthGroup, thirdGroup, secondGroup, tailGroup); // need to test group removal - at head, middle, and tail of the chain @@ -296,14 +296,14 @@ public void removeGroupInChain() { assertChain(fourthGroup, secondGroup); } - static final TagMap.BucketGroup fullGroup(int startingIndex) { + static final OptimizedTagMap.BucketGroup fullGroup(int startingIndex) { TagMap.Entry firstEntry = TagMap.Entry.newObjectEntry(tag(startingIndex), value(startingIndex)); TagMap.Entry secondEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); - TagMap.BucketGroup group = - new TagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); - for (int offset = 2; offset < TagMap.BucketGroup.LEN; ++offset) { + OptimizedTagMap.BucketGroup group = + new OptimizedTagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + for (int offset = 2; offset < OptimizedTagMap.BucketGroup.LEN; ++offset) { TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); group._insert(anotherEntry.hash(), anotherEntry); @@ -311,8 +311,8 @@ static final TagMap.BucketGroup fullGroup(int startingIndex) { return group; } - static final TagMap.BucketGroup fullGroup(int startingIndex, TagMap.BucketGroup prev) { - TagMap.BucketGroup group = fullGroup(startingIndex); + static final OptimizedTagMap.BucketGroup fullGroup(int startingIndex, OptimizedTagMap.BucketGroup prev) { + OptimizedTagMap.BucketGroup group = fullGroup(startingIndex); group.prev = prev; return group; } @@ -333,7 +333,7 @@ static final String value(int i) { return "value-i"; } - static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { + static void assertContainsDirectly(TagMap.Entry entry, OptimizedTagMap.BucketGroup group) { int hash = entry.hash(); String tag = entry.tag(); @@ -343,32 +343,32 @@ static void assertContainsDirectly(TagMap.Entry entry, TagMap.BucketGroup group) assertSame(group, group.findContainingGroupInChain(hash, tag)); } - static void assertDoesntContainDirectly(TagMap.Entry entry, TagMap.BucketGroup group) { - for (int i = 0; i < TagMap.BucketGroup.LEN; ++i) { + static void assertDoesntContainDirectly(TagMap.Entry entry, OptimizedTagMap.BucketGroup group) { + for (int i = 0; i < OptimizedTagMap.BucketGroup.LEN; ++i) { assertNotSame(entry, group._entryAt(i)); } } - static void assertChainContainsTag(String tag, TagMap.BucketGroup group) { - int hash = TagMap._hash(tag); + static void assertChainContainsTag(String tag, OptimizedTagMap.BucketGroup group) { + int hash = TagMap.Entry._hash(tag); assertNotNull(group.findInChain(hash, tag)); } - static void assertChainDoesntContainTag(String tag, TagMap.BucketGroup group) { - int hash = TagMap._hash(tag); + static void assertChainDoesntContainTag(String tag, OptimizedTagMap.BucketGroup group) { + int hash = TagMap.Entry._hash(tag); assertNull(group.findInChain(hash, tag)); } static void assertGroupContentsStrictEquals( - TagMap.BucketGroup expected, TagMap.BucketGroup actual) { - for (int i = 0; i < TagMap.BucketGroup.LEN; ++i) { + OptimizedTagMap.BucketGroup expected, OptimizedTagMap.BucketGroup actual) { + for (int i = 0; i < OptimizedTagMap.BucketGroup.LEN; ++i) { assertEquals(expected._hashAt(i), actual._hashAt(i)); assertSame(expected._entryAt(i), actual._entryAt(i)); } } - static void assertChain(TagMap.BucketGroup... chain) { - TagMap.BucketGroup cur; + static void assertChain(OptimizedTagMap.BucketGroup... chain) { + OptimizedTagMap.BucketGroup cur; int index; for (cur = chain[0], index = 0; cur != null; cur = cur.prev, ++index) { assertSame(chain[index], cur); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index 95f429737aa..f65bfe83f1e 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import datadog.trace.api.TagMap.Entry; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -16,10 +15,13 @@ import java.util.concurrent.ThreadFactory; import java.util.function.Function; import java.util.function.Supplier; + import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import datadog.trace.api.TagMap.Entry; + /** * Since TagMap.Entry is thread safe and has involves complicated multi-thread type resolution code, * this test uses a different approach to stress ordering different combinations. @@ -60,35 +62,34 @@ public void anyEntry_object() { } @ParameterizedTest - @ValueSource(booleans = {false, true}) + @ValueSource(booleans={false, true}) public void booleanEntry(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", value), - TagMap.Entry.BOOLEAN, - (entry) -> - multiCheck( - checkKey("foo", entry), - checkValue(value, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); + () -> TagMap.Entry.newBooleanEntry("foo", value), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @ParameterizedTest - @ValueSource(booleans = {false, true}) + @ValueSource(booleans= {false, true}) public void booleanEntry_boxed(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(value)), - TagMap.Entry.BOOLEAN, - (entry) -> - multiCheck( - checkKey("foo", entry), - checkValue(value, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); + () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(value)), + TagMap.Entry.BOOLEAN, + (entry) -> multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @ParameterizedTest - @ValueSource(booleans = {false, true}) + @ValueSource(booleans= {false, true}) public void anyEntry_boolean(boolean value) { test( () -> TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(value)), @@ -103,7 +104,7 @@ public void anyEntry_boolean(boolean value) { } @ParameterizedTest - @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void intEntry(int value) { test( () -> TagMap.Entry.newIntEntry("foo", value), @@ -117,7 +118,7 @@ public void intEntry(int value) { } @ParameterizedTest - @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void intEntry_boxed(int value) { test( () -> TagMap.Entry.newIntEntry("foo", Integer.valueOf(value)), @@ -131,7 +132,7 @@ public void intEntry_boxed(int value) { } @ParameterizedTest - @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void anyEntry_int(int value) { test( () -> TagMap.Entry.newAnyEntry("foo", Integer.valueOf(value)), @@ -146,22 +147,7 @@ public void anyEntry_int(int value) { } @ParameterizedTest - @ValueSource( - longs = { - Long.MIN_VALUE, - Integer.MIN_VALUE, - -1_048_576L, - -256L, - -128L, - -1L, - 0L, - 1L, - 128L, - 256L, - 1_048_576L, - Integer.MAX_VALUE, - Long.MAX_VALUE - }) + @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) public void longEntry(long value) { test( () -> TagMap.Entry.newLongEntry("foo", value), @@ -175,22 +161,7 @@ public void longEntry(long value) { } @ParameterizedTest - @ValueSource( - longs = { - Long.MIN_VALUE, - Integer.MIN_VALUE, - -1_048_576L, - -256L, - -128L, - -1L, - 0L, - 1L, - 128L, - 256L, - 1_048_576L, - Integer.MAX_VALUE, - Long.MAX_VALUE - }) + @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) public void longEntry_boxed(long value) { test( () -> TagMap.Entry.newLongEntry("foo", Long.valueOf(value)), @@ -204,22 +175,7 @@ public void longEntry_boxed(long value) { } @ParameterizedTest - @ValueSource( - longs = { - Long.MIN_VALUE, - Integer.MIN_VALUE, - -1_048_576L, - -256L, - -128L, - -1L, - 0L, - 1L, - 128L, - 256L, - 1_048_576L, - Integer.MAX_VALUE, - Long.MAX_VALUE - }) + @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) public void anyEntry_long(long value) { test( () -> TagMap.Entry.newAnyEntry("foo", Long.valueOf(value)), @@ -234,7 +190,7 @@ public void anyEntry_long(long value) { } @ParameterizedTest - @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void floatEntry(float value) { test( () -> TagMap.Entry.newFloatEntry("foo", value), @@ -248,7 +204,7 @@ public void floatEntry(float value) { } @ParameterizedTest - @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void floatEntry_boxed(float value) { test( () -> TagMap.Entry.newFloatEntry("foo", Float.valueOf(value)), @@ -262,7 +218,7 @@ public void floatEntry_boxed(float value) { } @ParameterizedTest - @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void anyEntry_float(float value) { test( () -> TagMap.Entry.newAnyEntry("foo", Float.valueOf(value)), @@ -276,8 +232,7 @@ public void anyEntry_float(float value) { } @ParameterizedTest - @ValueSource( - doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void doubleEntry(double value) { test( () -> TagMap.Entry.newDoubleEntry("foo", value), @@ -291,8 +246,7 @@ public void doubleEntry(double value) { } @ParameterizedTest - @ValueSource( - doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void doubleEntry_boxed(double value) { test( () -> TagMap.Entry.newDoubleEntry("foo", Double.valueOf(value)), @@ -306,8 +260,7 @@ public void doubleEntry_boxed(double value) { } @ParameterizedTest - @ValueSource( - doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void anyEntry_double(double value) { test( () -> TagMap.Entry.newAnyEntry("foo", Double.valueOf(value)), diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index 53164ba11ca..b8d2b084b1f 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -27,8 +27,8 @@ void testMerge() { TestCase mapACase = generateTest(); TestCase mapBCase = generateTest(); - TagMap tagMapA = test(mapACase); - TagMap tagMapB = test(mapBCase); + OptimizedTagMap tagMapA = test(mapACase); + OptimizedTagMap tagMapB = test(mapBCase); HashMap hashMapA = new HashMap<>(tagMapA); HashMap hashMapB = new HashMap<>(tagMapB); @@ -855,7 +855,7 @@ void priorFailingCase2() { put("key-41", "values--904162962")); Map expected = makeMap(testCase); - TagMap actual = makeTagMap(testCase); + OptimizedTagMap actual = makeTagMap(testCase); MapAction failingAction = remove("key-127"); failingAction.apply(expected); @@ -887,27 +887,27 @@ public static final Map makeMap(List actions) { return map; } - public static final TagMap makeTagMap(TestCase testCase) { + public static final OptimizedTagMap makeTagMap(TestCase testCase) { return makeTagMap(testCase.actions); } - public static final TagMap makeTagMap(MapAction... actions) { + public static final OptimizedTagMap makeTagMap(MapAction... actions) { return makeTagMap(Arrays.asList(actions)); } - public static final TagMap makeTagMap(List actions) { - TagMap map = new TagMap(); + public static final OptimizedTagMap makeTagMap(List actions) { + OptimizedTagMap map = new OptimizedTagMap(); for (MapAction action : actions) { action.apply(map); } return map; } - public static final TagMap test(TestCase test) { + public static final OptimizedTagMap test(TestCase test) { List actions = test.actions(); Map hashMap = new HashMap<>(); - TagMap tagMap = new TagMap(); + OptimizedTagMap tagMap = new OptimizedTagMap(); int actionIndex = 0; try { @@ -983,7 +983,7 @@ public static final MapAction remove(String key) { return new Remove(key); } - static final void assertMapEquals(Map expected, TagMap actual) { + static final void assertMapEquals(Map expected, OptimizedTagMap actual) { // checks entries in both directions to make sure there's full intersection for (Map.Entry expectedEntry : expected.entrySet()) { @@ -996,7 +996,7 @@ static final void assertMapEquals(Map expected, TagMap actual) { Object expectedValue = expected.get(actualEntry.tag()); assertEquals(expectedValue, actualEntry.objectValue()); } - + actual.checkIntegrity(); } @@ -1044,14 +1044,15 @@ static final Map mapOf(String... keysAndValues) { } static final TagMap tagMapOf(String... keysAndValues) { - TagMap map = new TagMap(); + OptimizedTagMap map = new OptimizedTagMap(); for (int i = 0; i < keysAndValues.length; i += 2) { String key = keysAndValues[i]; String value = keysAndValues[i + 1]; map.set(key, value); - // map.check(); } + map.checkIntegrity(); + return map; } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java index 6c4e49cb6bb..0d10b79ccff 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java @@ -9,6 +9,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; public class TagMapLedgerTest { static final int SIZE = 32; @@ -66,7 +68,27 @@ public void buildMutable() { for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } - assertEquals(SIZE, map.computeSize()); + assertEquals(SIZE, map.size()); + + // just proving that the map is mutable + map.set(key(1000), value(1000)); + } + + @ParameterizedTest + @EnumSource(TagMapType.class) + public void buildMutable(TagMapType mapType) { + TagMap.Ledger ledger = TagMap.ledger(); + for (int i = 0; i < SIZE; ++i) { + ledger.set(key(i), value(i)); + } + + assertEquals(SIZE, ledger.estimateSize()); + + TagMap map = ledger.build(mapType.factory); + for (int i = 0; i < SIZE; ++i) { + assertEquals(value(i), map.getString(key(i))); + } + assertEquals(SIZE, map.size()); // just proving that the map is mutable map.set(key(1000), value(1000)); @@ -85,7 +107,26 @@ public void buildImmutable() { for (int i = 0; i < SIZE; ++i) { assertEquals(value(i), map.getString(key(i))); } - assertEquals(SIZE, map.computeSize()); + assertEquals(SIZE, map.size()); + + assertFrozen(map); + } + + @ParameterizedTest + @EnumSource(TagMapType.class) + public void buildImmutable(TagMapType mapType) { + TagMap.Ledger ledger = TagMap.ledger(); + for (int i = 0; i < SIZE; ++i) { + ledger.set(key(i), value(i)); + } + + assertEquals(SIZE, ledger.estimateSize()); + + TagMap map = ledger.buildImmutable(mapType.factory); + for (int i = 0; i < SIZE; ++i) { + assertEquals(value(i), map.getString(key(i))); + } + assertEquals(SIZE, map.size()); assertFrozen(map); } @@ -97,6 +138,14 @@ public void build_empty() { assertNotSame(TagMap.EMPTY, ledger.build()); } + @ParameterizedTest + @EnumSource(TagMapType.class) + public void build_empty(TagMapType mapType) { + TagMap.Ledger ledger = TagMap.ledger(); + assertTrue(ledger.isDefinitelyEmpty()); + assertNotSame(mapType.empty(), ledger.build(mapType.factory)); + } + @Test public void buildImmutable_empty() { TagMap.Ledger ledger = TagMap.ledger(); @@ -112,7 +161,7 @@ public void isDefinitelyEmpty_emptyMap() { assertFalse(ledger.isDefinitelyEmpty()); TagMap map = ledger.build(); - assertTrue(map.checkIfEmpty()); + assertTrue(map.isEmpty()); } @Test diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 4ac773ed045..8fcfcafab8b 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -5,7 +5,6 @@ import static org.junit.Assert.assertSame; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Collection; @@ -15,15 +14,18 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; -import org.junit.jupiter.api.Test; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; public class TagMapTest { // size is chosen to make sure to stress all types of collisions in the Map static final int MANY_SIZE = 256; - @Test - public void map_put() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void map_put(TagMapType mapType) { + TagMap map = mapType.create(); Object prev = map.put("foo", "bar"); assertNull(prev); @@ -34,69 +36,85 @@ public void map_put() { assertNotEmpty(map); } - @Test - public void booleanEntry() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void booleanEntry(TagMapType mapType) { + TagMap map = mapType.create(); map.set("bool", false); TagMap.Entry entry = map.getEntry("bool"); - assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); + if ( map.isOptimized() ) { + assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); + } assertEquals(false, entry.booleanValue()); assertEquals(false, map.getBoolean("bool")); } - @Test - public void intEntry() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void intEntry(TagMapType mapType) { + TagMap map = mapType.create(); map.set("int", 42); TagMap.Entry entry = map.getEntry("int"); - assertEquals(TagMap.Entry.INT, entry.rawType); + if ( map.isOptimized() ) { + assertEquals(TagMap.Entry.INT, entry.rawType); + } assertEquals(42, entry.intValue()); assertEquals(42, map.getInt("int")); } - @Test - public void longEntry() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void longEntry(TagMapType mapType) { + TagMap map = mapType.create(); map.set("long", 42L); TagMap.Entry entry = map.getEntry("long"); - assertEquals(TagMap.Entry.LONG, entry.rawType); + if ( map.isOptimized() ) { + assertEquals(TagMap.Entry.LONG, entry.rawType); + } assertEquals(42L, entry.longValue()); assertEquals(42L, map.getLong("long")); } - @Test - public void floatEntry() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void floatEntry(TagMapType mapType) { + TagMap map = mapType.create(); map.set("float", 3.14F); TagMap.Entry entry = map.getEntry("float"); - assertEquals(TagMap.Entry.FLOAT, entry.rawType); + if ( map.isOptimized() ) { + assertEquals(TagMap.Entry.FLOAT, entry.rawType); + } assertEquals(3.14F, entry.floatValue()); assertEquals(3.14F, map.getFloat("float")); } - @Test - public void doubleEntry() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void doubleEntry(TagMapType mapType) { + TagMap map = mapType.create(); map.set("double", Math.PI); TagMap.Entry entry = map.getEntry("double"); - assertEquals(TagMap.Entry.DOUBLE, entry.rawType); + if ( map.isOptimized() ) { + assertEquals(TagMap.Entry.DOUBLE, entry.rawType); + } assertEquals(Math.PI, entry.doubleValue()); assertEquals(Math.PI, map.getDouble("double")); } - @Test - public void empty() { - TagMap empty = TagMap.EMPTY; + @ParameterizedTest + @EnumSource(TagMapType.class) + public void empty(TagMapType mapType) { + TagMap empty = mapType.empty(); assertFrozen(empty); assertNull(empty.getEntry("foo")); @@ -104,22 +122,24 @@ public void empty() { assertEmpty(empty); } - @Test - public void putAll_empty() { - // TagMap.EMPTY breaks the rules and uses a different size bucket array - // This test is just to verify that the commonly use putAll still works with EMPTY - TagMap newMap = new TagMap(); - newMap.putAll(TagMap.EMPTY); - - assertSize(0, newMap); - assertEmpty(newMap); + @ParameterizedTest + @EnumSource(TagMapTypePair.class) + public void putAll_empty(TagMapTypePair mapTypePair) { + // TagMap.EMPTY breaks the rules and uses a different size bucket array + // This test is just to verify that the commonly use putAll still works with EMPTY + TagMap newMap = mapTypePair.firstType.create(); + newMap.putAll(mapTypePair.secondType.empty()); + + assertSize(0, newMap); + assertEmpty(newMap); } - @Test - public void clear() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void clear(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); assertSize(size, map); assertNotEmpty(map); @@ -128,9 +148,10 @@ public void clear() { assertEmpty(map); } - @Test - public void map_put_replacement() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void map_put_replacement(TagMapType mapType) { + TagMap map = mapType.create(); Object prev1 = map.put("foo", "bar"); assertNull(prev1); @@ -145,11 +166,12 @@ public void map_put_replacement() { assertEntry("foo", "baz", map); } - @Test - public void map_remove() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void map_remove(TagMapType mapType) { + TagMap map = mapType.create(); - Object prev1 = map.remove("foo"); + Object prev1 = map.remove((Object)"foo"); assertNull(prev1); map.put("foo", "bar"); @@ -157,15 +179,16 @@ public void map_remove() { assertSize(1, map); assertNotEmpty(map); - Object prev2 = map.remove("foo"); + Object prev2 = map.remove((Object)"foo"); assertEquals("bar", prev2); assertSize(0, map); assertEmpty(map); } - @Test - public void freeze() { - TagMap map = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapType.class) + public void freeze(TagMapType mapType) { + TagMap map = mapType.create(); map.put("foo", "bar"); assertEntry("foo", "bar", map); @@ -180,9 +203,10 @@ public void freeze() { assertEntry("foo", "bar", map); } - @Test - public void emptyMap() { - TagMap map = TagMap.EMPTY; + @ParameterizedTest + @EnumSource(TagMapType.class) + public void emptyMap(TagMapType mapType) { + TagMap map = mapType.empty(); assertSize(0, map); assertEmpty(map); @@ -190,10 +214,11 @@ public void emptyMap() { assertFrozen(map); } - @Test - public void putMany() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void putMany(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), map); @@ -203,12 +228,13 @@ public void putMany() { assertSize(size, map); } - @Test - public void copyMany() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void copyMany(TagMapType mapType) { int size = randomSize(); - TagMap orig = createTagMap(size); + TagMap orig = createTagMap(mapType, size); assertSize(size, orig); - + TagMap copy = orig.copy(); orig.clear(); // doing this to make sure that copied isn't modified @@ -217,25 +243,27 @@ public void copyMany() { } assertSize(size, copy); } - - @Test - public void immutableCopy() { - int size = randomSize(); - TagMap orig = createTagMap(size); - - TagMap immutableCopy = orig.immutableCopy(); - orig.clear(); // doing this to make sure that copied isn't modified - + + @ParameterizedTest + @EnumSource(TagMapType.class) + public void immutableCopy(TagMapType mapType) { + int size = randomSize(); + TagMap orig = createTagMap(mapType, size); + + TagMap immutableCopy = orig.immutableCopy(); + orig.clear(); // doing this to make sure that copied isn't modified + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), immutableCopy); } assertSize(size, immutableCopy); } - @Test - public void replaceALot() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void replaceALot(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); for (int i = 0; i < size; ++i) { int index = ThreadLocalRandom.current().nextInt(size); @@ -245,24 +273,29 @@ public void replaceALot() { } } - @Test - public void shareEntry() { - TagMap orig = new TagMap(); + @ParameterizedTest + @EnumSource(TagMapTypePair.class) + public void shareEntry(TagMapTypePair mapTypePair) { + TagMap orig = mapTypePair.firstType.create(); orig.set("foo", "bar"); - TagMap dest = new TagMap(); - dest.putEntry(orig.getEntry("foo")); - - assertSame(orig.getEntry("foo"), dest.getEntry("foo")); + TagMap dest = mapTypePair.secondType.create(); + dest.set(orig.getEntry("foo")); + + assertEquals(orig.getEntry("foo"), dest.getEntry("foo")); + if ( mapTypePair == TagMapTypePair.BOTH_OPTIMIZED ) { + assertSame(orig.getEntry("foo"), dest.getEntry("foo")); + } } - @Test - public void putAll_clobberAll() { + @ParameterizedTest + @EnumSource(TagMapTypePair.class) + public void putAll_clobberAll(TagMapTypePair mapTypePair) { int size = randomSize(); - TagMap orig = createTagMap(size); + TagMap orig = createTagMap(mapTypePair.firstType, size); assertSize(size, orig); - TagMap dest = new TagMap(); + TagMap dest = mapTypePair.secondType.create(); for (int i = size - 1; i >= 0; --i) { dest.set(key(i), altValue(i)); } @@ -273,19 +306,20 @@ public void putAll_clobberAll() { for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), dest); } - assertSize(size, dest); + assertSize(size, dest); } - - @Test - public void putAll_clobberAndExtras() { + + @ParameterizedTest + @EnumSource(TagMapTypePair.class) + public void putAll_clobberAndExtras(TagMapTypePair mapTypePair) { int size = randomSize(); - TagMap orig = createTagMap(size); + TagMap orig = createTagMap(mapTypePair.firstType, size); assertSize(size, orig); - TagMap dest = new TagMap(); + TagMap dest = mapTypePair.secondType.create(); for (int i = size / 2 - 1; i >= 0; --i) { dest.set(key(i), altValue(i)); - } + } // This should clobber all the values in dest dest.putAll(orig); @@ -293,14 +327,15 @@ public void putAll_clobberAndExtras() { for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), dest); } - - assertSize(size, dest); + + assertSize(size, dest); } - @Test - public void removeMany() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void removeMany(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), map); @@ -310,20 +345,21 @@ public void removeMany() { assertSize(size, map); for (int i = 0; i < size; ++i) { - Object removedValue = map.remove(key(i)); + Object removedValue = map.remove((Object)key(i)); assertEquals(value(i), removedValue); // not doing exhaustive size checks - assertEquals(size - i - 1, map.computeSize()); + assertEquals(size - i - 1, map.size()); } assertEmpty(map); } - @Test - public void fillMap() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void fillMap(TagMapType mapType) { int size = randomSize(); - TagMap map = new TagMap(); + TagMap map = mapType.create(); for (int i = 0; i < size; ++i) { map.set(key(i), i); } @@ -337,10 +373,11 @@ public void fillMap() { assertTrue(hashMap.isEmpty()); } - @Test - public void fillStringMap() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void fillStringMap(TagMapType mapType) { int size = randomSize(); - TagMap map = new TagMap(); + TagMap map = mapType.create(); for (int i = 0; i < size; ++i) { map.set(key(i), i); } @@ -354,10 +391,11 @@ public void fillStringMap() { assertTrue(hashMap.isEmpty()); } - @Test - public void iterator() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void iterator(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); Set keys = new HashSet<>(); for (TagMap.Entry entry : map) { @@ -374,10 +412,11 @@ public void iterator() { assertTrue(keys.isEmpty()); } - @Test - public void forEachConsumer() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void forEachConsumer(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); Set keys = new HashSet<>(size); map.forEach((entry) -> keys.add(entry.tag())); @@ -391,10 +430,11 @@ public void forEachConsumer() { assertTrue(keys.isEmpty()); } - @Test - public void forEachBiConsumer() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void forEachBiConsumer(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); Set keys = new HashSet<>(size); map.forEach(keys, (k, entry) -> k.add(entry.tag())); @@ -408,10 +448,11 @@ public void forEachBiConsumer() { assertTrue(keys.isEmpty()); } - @Test - public void forEachTriConsumer() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void forEachTriConsumer(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); Set keys = new HashSet<>(size); map.forEach(keys, "hi", (k, msg, entry) -> keys.add(entry.tag())); @@ -425,10 +466,11 @@ public void forEachTriConsumer() { assertTrue(keys.isEmpty()); } - @Test - public void entrySet() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void entrySet(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); Set> actualEntries = map.entrySet(); assertEquals(size, actualEntries.size()); @@ -441,10 +483,11 @@ public void entrySet() { assertTrue(expectedKeys.isEmpty()); } - @Test - public void keySet() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void keySet(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); Set actualKeys = map.keySet(); assertEquals(size, actualKeys.size()); @@ -457,10 +500,11 @@ public void keySet() { assertTrue(expectedKeys.isEmpty()); } - @Test - public void values() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void values(TagMapType mapType) { int size = randomSize(); - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); Collection actualValues = map.values(); assertEquals(size, actualValues.size()); @@ -473,29 +517,24 @@ public void values() { assertTrue(expectedValues.isEmpty()); } - @Test - public void _toString() { + @ParameterizedTest + @EnumSource(TagMapType.class) + public void _toString(TagMapType mapType) { int size = 4; - TagMap map = createTagMap(size); + TagMap map = createTagMap(mapType, size); assertEquals("{key-1=value-1, key-0=value-0, key-3=value-3, key-2=value-2}", map.toString()); } - @Test - public void toInternalString() { - TagMap map = createTagMap(128); - assertNotEquals("", map.toInternalString()); - } - static final int randomSize() { return ThreadLocalRandom.current().nextInt(1, MANY_SIZE); } - static final TagMap createTagMap() { - return createTagMap(randomSize()); + static final TagMap createTagMap(TagMapType mapType) { + return createTagMap(mapType, randomSize()); } - static final TagMap createTagMap(int size) { - TagMap map = new TagMap(); + static final TagMap createTagMap(TagMapType mapType, int size) { + TagMap map = mapType.create(); for (int i = 0; i < size; ++i) { map.set(key(i), value(i)); } @@ -563,7 +602,9 @@ static final void assertEntry(String key, String value, TagMap map) { } static final void assertSize(int size, TagMap map) { - assertEquals(size, map.computeSize()); + if ( map instanceof OptimizedTagMap ) { + assertEquals(size, ((OptimizedTagMap)map).computeSize()); + } assertEquals(size, map.size()); assertEquals(size, count(map)); @@ -574,12 +615,16 @@ static final void assertSize(int size, TagMap map) { } static final void assertNotEmpty(TagMap map) { - assertFalse(map.checkIfEmpty()); + if ( map instanceof OptimizedTagMap ) { + assertFalse(((OptimizedTagMap)map).checkIfEmpty()); + } assertFalse(map.isEmpty()); } static final void assertEmpty(TagMap map) { - assertTrue(map.checkIfEmpty()); + if ( map instanceof OptimizedTagMap ) { + assertTrue(((OptimizedTagMap)map).checkIfEmpty()); + } assertTrue(map.isEmpty()); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapType.java b/internal-api/src/test/java/datadog/trace/api/TagMapType.java new file mode 100644 index 00000000000..4cb62ebedcb --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapType.java @@ -0,0 +1,20 @@ +package datadog.trace.api; + +public enum TagMapType { + OPTIMIZED(new OptimizedTagMapFactory()), + LEGACY(new LegacyTagMapFactory()); + + final TagMapFactory factory; + + TagMapType(TagMapFactory factory) { + this.factory = factory; + } + + public final TagMap create() { + return factory.create(); + } + + public final TagMap empty() { + return factory.empty(); + } +} \ No newline at end of file diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java b/internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java new file mode 100644 index 00000000000..788bf662d78 --- /dev/null +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java @@ -0,0 +1,16 @@ +package datadog.trace.api; + +public enum TagMapTypePair { + BOTH_OPTIMIZED(TagMapType.OPTIMIZED, TagMapType.OPTIMIZED), + BOTH_LEGACY(TagMapType.LEGACY, TagMapType.LEGACY), + OPTIMIZED_LEGACY(TagMapType.OPTIMIZED, TagMapType.LEGACY), + LEGACY_OPTIMIZED(TagMapType.LEGACY, TagMapType.OPTIMIZED); + + public final TagMapType firstType; + public final TagMapType secondType; + + TagMapTypePair(TagMapType firstType, TagMapType secondType) { + this.firstType = firstType; + this.secondType = secondType; + } +} \ No newline at end of file From 648c15cb8fd1c49ebafacc444208aa707397f506 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 8 May 2025 16:09:49 -0400 Subject: [PATCH 80/84] spotless --- .../main/java/datadog/trace/api/TagMap.java | 649 +++++++++--------- .../trace/api/TagMapBucketGroupTest.java | 12 +- .../datadog/trace/api/TagMapEntryTest.java | 113 ++- .../datadog/trace/api/TagMapFuzzTest.java | 4 +- .../datadog/trace/api/TagMapLedgerTest.java | 6 +- .../java/datadog/trace/api/TagMapTest.java | 77 +-- .../java/datadog/trace/api/TagMapType.java | 14 +- .../datadog/trace/api/TagMapTypePair.java | 10 +- 8 files changed, 462 insertions(+), 423 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 4610f4970d8..94e527fafdf 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1,5 +1,6 @@ package datadog.trace.api; +import datadog.trace.api.function.TriConsumer; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Arrays; @@ -17,8 +18,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import datadog.trace.api.function.TriConsumer; - /** * A super simple hash map designed for... * @@ -83,15 +82,15 @@ public static TagMap fromMapImmutable(Map map) { return fromMap(map).freeze(); } } - + static TagMap create() { - return TagMapFactory.INSTANCE.create(); + return TagMapFactory.INSTANCE.create(); } - + static TagMap create(int size) { - return TagMapFactory.INSTANCE.create(size); + return TagMapFactory.INSTANCE.create(size); } - + /** Creates a new TagMap.Ledger */ public static Ledger ledger() { return new Ledger(); @@ -101,18 +100,18 @@ public static Ledger ledger() { public static Ledger ledger(int size) { return new Ledger(size); } - + boolean isOptimized(); - + @Deprecated Set keySet(); - + @Deprecated Collection values(); // @Deprecated -- not deprecated until OptimizedTagMap becomes the default Set> entrySet(); - + @Deprecated Object get(Object tag); @@ -132,19 +131,16 @@ public static Ledger ledger(int size) { double getDouble(String tag); - /** - * Provides the corresponding Entry object - preferable w/ optimized TagMap if the Entry needs - * to have its type checked + * Provides the corresponding Entry object - preferable w/ optimized TagMap if the Entry needs to + * have its type checked */ Entry getEntry(String tag); - + @Deprecated Object put(String tag, Object value); - /** - * Sets value without returning prior value - optimal for legacy & optimized implementations - */ + /** Sets value without returning prior value - optimal for legacy & optimized implementations */ void set(String tag, Object value); /** @@ -161,68 +157,65 @@ public static Ledger ledger(int size) { void set(String tag, long value); void set(String tag, float value); - + void set(String tag, double value); - + void set(Entry newEntry); - - /** - * sets the value while returning the prior Entry - */ + + /** sets the value while returning the prior Entry */ Entry getAndSet(String tag, Object value); - + Entry getAndSet(String tag, CharSequence value); - + Entry getAndSet(String tag, boolean value); - + Entry getAndSet(String tag, int value); - + Entry getAndSet(String tag, long value); - + Entry getAndSet(String tag, float value); - + Entry getAndSet(String tag, double value); - /** - * TagMap specific method that places an Entry directly into an optimized TagMap avoiding - * need to allocate a new Entry object + * TagMap specific method that places an Entry directly into an optimized TagMap avoiding need to + * allocate a new Entry object */ Entry getAndSet(Entry newEntry); - + void putAll(Map map); - + /** * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from one TagMap to another * - *

        For optimized TagMaps, this method takes advantage of the consistent TagMap layout to - * quickly handle each bucket. And similar to {@link TagMap#(Entry)} this method shares - * Entry objects from the source TagMap + *

        For optimized TagMaps, this method takes advantage of the consistent TagMap layout to + * quickly handle each bucket. And similar to {@link TagMap#(Entry)} this method shares Entry + * objects from the source TagMap */ void putAll(TagMap that); - + void fillMap(Map map); - + void fillStringMap(Map stringMap); - + @Deprecated Object remove(Object tag); - + /** - * Similar to {@link Map#remove(Object)} but doesn't return the prior value (orEntry). - * Preferred when prior value isn't needed - best for both legacy and optimal TagMaps + * Similar to {@link Map#remove(Object)} but doesn't return the prior value (orEntry). Preferred + * when prior value isn't needed - best for both legacy and optimal TagMaps */ boolean remove(String tag); /** * Similar to {@link Map#remove(Object)} but returns the prior Entry object rather than the prior - * value. For optimized TagMap-s, this preferred because it avoids additional boxing. + * value. For optimized TagMap-s, this preferred because it avoids additional boxing. */ Entry getAndRemove(String tag); /** Returns a mutable copy of this TagMap */ TagMap copy(); - + /** * Returns an immutable copy of this TagMap This method is more efficient than * map.copy().freeze() when called on an immutable TagMap @@ -257,7 +250,7 @@ public static Ledger ledger(int size) { * *

        The intention is to use this method to avoid using a capturing lambda */ - void forEach(T thisObj, U otherObj, TriConsumer consumer); + void forEach(T thisObj, U otherObj, TriConsumer consumer); /** Clears the TagMap */ void clear(); @@ -327,7 +320,7 @@ public static final class Entry extends EntryChange implements Map.Entry entry) { return newAnyEntry(entry.getKey(), entry.getValue()); } @@ -794,18 +787,18 @@ public Object getValue() { public Object setValue(Object value) { throw new UnsupportedOperationException(); } - + @Override public final int hashCode() { return this.hash(); } - + @Override public boolean equals(Object obj) { - if ( !(obj instanceof TagMap.Entry) ) return false; - - TagMap.Entry that = (TagMap.Entry)obj; - return this.tag.equals(that.tag) && this.objectValue().equals(that.objectValue()); + if (!(obj instanceof TagMap.Entry)) return false; + + TagMap.Entry that = (TagMap.Entry) obj; + return this.tag.equals(that.tag) && this.objectValue().equals(that.objectValue()); } private static final long boolean2Prim(boolean value) { @@ -993,16 +986,16 @@ public TagMap build() { fill(map); return map; } - + TagMap build(TagMapFactory mapFactory) { TagMap map = mapFactory.create(this.estimateSize()); fill(map); return map; } - + void fill(TagMap map) { EntryChange[] entryChanges = this.entryChanges; - int size = this.nextPos; + int size = this.nextPos; for (int i = 0; i < size && i < entryChanges.length; ++i) { EntryChange change = entryChanges[i]; @@ -1013,13 +1006,13 @@ void fill(TagMap map) { } } } - + TagMap buildImmutable(TagMapFactory mapFactory) { if (this.nextPos == 0) { return mapFactory.empty(); } else { return this.build(mapFactory).freeze(); - } + } } public TagMap buildImmutable() { @@ -1064,49 +1057,48 @@ public EntryChange next() { */ abstract class TagMapFactory { public static final TagMapFactory INSTANCE = new OptimizedTagMapFactory(); - + public abstract MapT create(); - + public abstract MapT create(int size); - + public abstract MapT empty(); } final class OptimizedTagMapFactory extends TagMapFactory { @Override public final OptimizedTagMap create() { - return new OptimizedTagMap(); + return new OptimizedTagMap(); } - + @Override public OptimizedTagMap create(int size) { - return new OptimizedTagMap(); + return new OptimizedTagMap(); } - + @Override public OptimizedTagMap empty() { - return OptimizedTagMap.EMPTY; + return OptimizedTagMap.EMPTY; } } final class LegacyTagMapFactory extends TagMapFactory { @Override public final LegacyTagMap create() { - return new LegacyTagMap(); + return new LegacyTagMap(); } - + @Override public LegacyTagMap create(int size) { - return new LegacyTagMap(size); + return new LegacyTagMap(size); } - + @Override public LegacyTagMap empty() { - return LegacyTagMap.EMPTY; + return LegacyTagMap.EMPTY; } } - final class OptimizedTagMap implements TagMap { // Using special constructor that creates a frozen view of an existing array // Bucket calculation requires that array length is a power of 2 @@ -1130,10 +1122,10 @@ private OptimizedTagMap(Object[] buckets, int size) { this.size = size; this.frozen = true; } - + @Override public final boolean isOptimized() { - return true; + return true; } @Override @@ -1153,7 +1145,7 @@ public final Object get(Object tag) { return this.getObject((String) tag); } - + /** Provides the corresponding entry value as an Object - boxing if necessary */ public final Object getObject(String tag) { Entry entry = this.getEntry(tag); @@ -1200,7 +1192,7 @@ public boolean containsKey(Object key) { @Override public boolean containsValue(Object value) { - // This could be optimized - but probably isn't called enough to be worth it + // This could be optimized - but probably isn't called enough to be worth it for (Entry entry : this) { if (entry.objectValue().equals(value)) return true; } @@ -1210,17 +1202,17 @@ public boolean containsValue(Object value) { @Override public Set keySet() { return new Keys(this); - } - + } + @Override public Collection values() { return new Values(this); } - + @Override public Set> entrySet() { return new Entries(this); - } + } @Override public final Entry getEntry(String tag) { @@ -1250,17 +1242,17 @@ public final Object put(String tag, Object value) { TagMap.Entry entry = this.getAndSet(Entry.newAnyEntry(tag, value)); return entry == null ? null : entry.objectValue(); } - + @Override public final void set(TagMap.Entry newEntry) { - this.getAndSet(newEntry); + this.getAndSet(newEntry); } - + @Override public final void set(String tag, Object value) { - this.getAndSet(Entry.newAnyEntry(tag, value)); + this.getAndSet(Entry.newAnyEntry(tag, value)); } - + @Override public final void set(String tag, CharSequence value) { this.getAndSet(Entry.newObjectEntry(tag, value)); @@ -1289,8 +1281,8 @@ public final void set(String tag, float value) { @Override public final void set(String tag, double value) { this.getAndSet(Entry.newDoubleEntry(tag, value)); - } - + } + @Override public final Entry getAndSet(Entry newEntry) { this.checkWriteAccess(); @@ -1339,17 +1331,17 @@ public final Entry getAndSet(Entry newEntry) { // unreachable return null; } - + @Override public Entry getAndSet(String tag, Object value) { - return this.getAndSet(Entry.newAnyEntry(tag, value)); + return this.getAndSet(Entry.newAnyEntry(tag, value)); } - + @Override public Entry getAndSet(String tag, CharSequence value) { - return this.getAndSet(Entry.newObjectEntry(tag, value)); + return this.getAndSet(Entry.newObjectEntry(tag, value)); } - + @Override public final TagMap.Entry getAndSet(String tag, boolean value) { return this.getAndSet(Entry.newBooleanEntry(tag, value)); @@ -1374,7 +1366,7 @@ public final TagMap.Entry getAndSet(String tag, float value) { public final TagMap.Entry getAndSet(String tag, double value) { return this.getAndSet(Entry.newDoubleEntry(tag, value)); } - + public final void putAll(Map map) { this.checkWriteAccess(); @@ -1384,7 +1376,7 @@ public final void putAll(Map map) { this.putAllUnoptimizedMap(map); } } - + private final void putAllUnoptimizedMap(Map that) { for (Map.Entry entry : that.entrySet()) { // use set which returns a prior Entry rather put which may box a prior primitive value @@ -1395,20 +1387,20 @@ private final void putAllUnoptimizedMap(Map /** * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from one TagMap to another * - *

        For optimized TagMaps, this method takes advantage of the consistent TagMap layout to + *

        For optimized TagMaps, this method takes advantage of the consistent TagMap layout to * quickly handle each bucket. And similar to {@link TagMap#putEntry(Entry)} this method shares * Entry objects from the source TagMap */ public final void putAll(TagMap that) { this.checkWriteAccess(); - + if (that instanceof OptimizedTagMap) { this.putAllOptimizedMap((OptimizedTagMap) that); } else { this.putAllUnoptimizedMap(that); } } - + private final void putAllOptimizedMap(OptimizedTagMap that) { if (this.size == 0) { this.putAllIntoEmptyMap(that); @@ -1592,7 +1584,7 @@ public final void fillStringMap(Map stringMap) { thisGroup.fillStringMapFromChain(stringMap); } } - } + } @Override public final Object remove(Object tag) { @@ -1601,11 +1593,11 @@ public final Object remove(Object tag) { Entry entry = this.getAndRemove((String) tag); return entry == null ? null : entry.objectValue(); } - + public final boolean remove(String tag) { - return (this.getAndRemove(tag) != null); + return (this.getAndRemove(tag) != null); } - + @Override public final Entry getAndRemove(String tag) { this.checkWriteAccess(); @@ -1645,14 +1637,14 @@ public final Entry getAndRemove(String tag) { } return null; } - + @Override public final TagMap copy() { - OptimizedTagMap copy = new OptimizedTagMap(); + OptimizedTagMap copy = new OptimizedTagMap(); copy.putAllIntoEmptyMap(this); return copy; } - + public final TagMap immutableCopy() { if (this.frozen) { return this; @@ -1660,7 +1652,7 @@ public final TagMap immutableCopy() { return this.copy().freeze(); } } - + @Override public final Iterator iterator() { return new EntryIterator(this); @@ -1670,8 +1662,8 @@ public final Iterator iterator() { public final Stream stream() { return StreamSupport.stream(spliterator(), false); } - - @Override + + @Override public final void forEach(Consumer consumer) { Object[] thisBuckets = this.buckets; @@ -1708,7 +1700,7 @@ public final void forEach(T thisObj, BiConsumer con } } } - + @Override public final void forEach( T thisObj, U otherObj, TriConsumer consumer) { @@ -1728,7 +1720,7 @@ public final void forEach( } } } - + public final void clear() { this.checkWriteAccess(); @@ -1829,32 +1821,31 @@ final boolean checkIfEmpty() { return true; } - + @Override public Object compute( - String key, BiFunction remappingFunction) - { - this.checkWriteAccess(); - - return TagMap.super.compute(key, remappingFunction); + String key, BiFunction remappingFunction) { + this.checkWriteAccess(); + + return TagMap.super.compute(key, remappingFunction); } - + @Override - public Object computeIfAbsent(String key, Function mappingFunction) { - this.checkWriteAccess(); - - return TagMap.super.computeIfAbsent(key, mappingFunction); + public Object computeIfAbsent( + String key, Function mappingFunction) { + this.checkWriteAccess(); + + return TagMap.super.computeIfAbsent(key, mappingFunction); } - + @Override - public Object computeIfPresent(String key, - BiFunction remappingFunction) - { - this.checkWriteAccess(); - - return TagMap.super.computeIfPresent(key, remappingFunction); + public Object computeIfPresent( + String key, BiFunction remappingFunction) { + this.checkWriteAccess(); + + return TagMap.super.computeIfPresent(key, remappingFunction); } - + @Override public final String toString() { return toPrettyString(); @@ -1908,7 +1899,6 @@ final String toInternalString() { } return ledger.toString(); } - abstract static class MapIterator implements Iterator { private final Object[] buckets; @@ -2566,34 +2556,34 @@ public Object next() { final class LegacyTagMap extends HashMap implements TagMap { private static final long serialVersionUID = 77473435283123683L; - static final LegacyTagMap EMPTY = new LegacyTagMap().freeze(); + static final LegacyTagMap EMPTY = new LegacyTagMap().freeze(); private boolean frozen = false; - + LegacyTagMap() { - super(); - } + super(); + } LegacyTagMap(int capacity) { - super(capacity); + super(capacity); } - + LegacyTagMap(LegacyTagMap that) { - super(that); + super(that); } - + @Override public boolean isOptimized() { - return false; + return false; } - + @Override public void clear() { - this.checkWriteAccess(); - - super.clear(); + this.checkWriteAccess(); + + super.clear(); } - + public final LegacyTagMap freeze() { this.frozen = true; @@ -2607,320 +2597,319 @@ public boolean isFrozen() { public final void checkWriteAccess() { if (this.frozen) throw new IllegalStateException("TagMap frozen"); } - + @Override public final TagMap copy() { - return new LegacyTagMap(this); + return new LegacyTagMap(this); } - + @Override public final void fillMap(Map map) { - map.putAll(this); + map.putAll(this); } - + @Override public final void fillStringMap(Map stringMap) { - for ( Map.Entry entry: this.entrySet() ) { - stringMap.put(entry.getKey(), entry.getValue().toString()); - } + for (Map.Entry entry : this.entrySet()) { + stringMap.put(entry.getKey(), entry.getValue().toString()); + } } - + @Override public final void forEach(Consumer consumer) { - for ( Map.Entry entry: this.entrySet() ) { - consumer.accept(TagMap.Entry.newAnyEntry(entry)); - } + for (Map.Entry entry : this.entrySet()) { + consumer.accept(TagMap.Entry.newAnyEntry(entry)); + } } - + @Override - public final void forEach(T thisObj, BiConsumer consumer) { - for ( Map.Entry entry: this.entrySet() ) { - consumer.accept(thisObj, TagMap.Entry.newAnyEntry(entry)); - } + public final void forEach( + T thisObj, BiConsumer consumer) { + for (Map.Entry entry : this.entrySet()) { + consumer.accept(thisObj, TagMap.Entry.newAnyEntry(entry)); + } } - + @Override - public final void forEach(T thisObj, U otherObj, - TriConsumer consumer) - { - for ( Map.Entry entry: this.entrySet() ) { - consumer.accept(thisObj, otherObj, TagMap.Entry.newAnyEntry(entry)); - } + public final void forEach( + T thisObj, U otherObj, TriConsumer consumer) { + for (Map.Entry entry : this.entrySet()) { + consumer.accept(thisObj, otherObj, TagMap.Entry.newAnyEntry(entry)); + } } @Override public final Object getObject(String tag) { - return this.get(tag); + return this.get(tag); } - + @Override public final boolean getBoolean(String tag) { - Object result = this.get(tag); - if ( result == null ) return false; - - if ( result instanceof Boolean ) { - return (Boolean)result; - } else if ( result instanceof Number ) { - Number number = (Number)result; - return (number.intValue() != 0); - } else { - return true; - } - } - + Object result = this.get(tag); + if (result == null) return false; + + if (result instanceof Boolean) { + return (Boolean) result; + } else if (result instanceof Number) { + Number number = (Number) result; + return (number.intValue() != 0); + } else { + return true; + } + } + @Override public final TagMap.Entry getAndSet(String tag, Object value) { - Object prior = this.put(tag, value); - return prior == null ? null : TagMap.Entry.newAnyEntry(tag, prior); + Object prior = this.put(tag, value); + return prior == null ? null : TagMap.Entry.newAnyEntry(tag, prior); } - + @Override public final TagMap.Entry getAndSet(String tag, CharSequence value) { Object prior = this.put(tag, value); return prior == null ? null : TagMap.Entry.newAnyEntry(tag, prior); } - + @Override public final TagMap.Entry getAndSet(String tag, boolean value) { - return this.getAndSet(tag, Boolean.valueOf(value)); + return this.getAndSet(tag, Boolean.valueOf(value)); } - + @Override public final TagMap.Entry getAndSet(String tag, double value) { - return this.getAndSet(tag, Double.valueOf(value)); + return this.getAndSet(tag, Double.valueOf(value)); } - + @Override public final TagMap.Entry getAndSet(String tag, float value) { - return this.getAndSet(tag, Float.valueOf(value)); + return this.getAndSet(tag, Float.valueOf(value)); } - + @Override public final TagMap.Entry getAndSet(String tag, int value) { - return this.getAndSet(tag, Integer.valueOf(value)); + return this.getAndSet(tag, Integer.valueOf(value)); } - + @Override public final TagMap.Entry getAndSet(String tag, long value) { - return this.getAndSet(tag, Long.valueOf(value)); + return this.getAndSet(tag, Long.valueOf(value)); } - + @Override public final TagMap.Entry getAndSet(TagMap.Entry newEntry) { - return this.getAndSet(newEntry.tag(), newEntry.objectValue()); + return this.getAndSet(newEntry.tag(), newEntry.objectValue()); } - + @Override public final TagMap.Entry getAndRemove(String tag) { - Object prior = this.remove((Object)tag); + Object prior = this.remove((Object) tag); return prior == null ? null : TagMap.Entry.newAnyEntry(tag, prior); } - + @Override public final double getDouble(String tag) { - Object value = this.get(tag); - if ( value instanceof Number ) { - return ((Number)value).doubleValue(); - } else if ( value instanceof Boolean ) { - return ((Boolean)value) ? 1D : 0D; - } else { - return 0D; - } + Object value = this.get(tag); + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } else if (value instanceof Boolean) { + return ((Boolean) value) ? 1D : 0D; + } else { + return 0D; + } } - + @Override public final long getLong(String tag) { - Object value = this.get(tag); - if ( value == null ) { - return 0L; - } else if ( value instanceof Number ) { - return ((Number)value).longValue(); - } else if ( value instanceof Boolean ) { - return ((Boolean)value) ? 1L : 0L; - } else { - return 0L; - } - } - + Object value = this.get(tag); + if (value == null) { + return 0L; + } else if (value instanceof Number) { + return ((Number) value).longValue(); + } else if (value instanceof Boolean) { + return ((Boolean) value) ? 1L : 0L; + } else { + return 0L; + } + } + @Override public final float getFloat(String tag) { - Object value = this.get(tag); - if ( value == null ) { - return 0F; - } else if ( value instanceof Number ) { - return ((Number)value).floatValue(); - } else if ( value instanceof Boolean ) { - return ((Boolean)value) ? 1F : 0F; - } else { - return 0F; - } - } - + Object value = this.get(tag); + if (value == null) { + return 0F; + } else if (value instanceof Number) { + return ((Number) value).floatValue(); + } else if (value instanceof Boolean) { + return ((Boolean) value) ? 1F : 0F; + } else { + return 0F; + } + } + @Override public final int getInt(String tag) { - Object value = this.get(tag); - if ( value == null ) { - return 0; - } else if ( value instanceof Number ) { - return ((Number)value).intValue(); - } else if ( value instanceof Boolean ) { - return ((Boolean)value) ? 1 : 0; - } else { - return 0; - } - } - + Object value = this.get(tag); + if (value == null) { + return 0; + } else if (value instanceof Number) { + return ((Number) value).intValue(); + } else if (value instanceof Boolean) { + return ((Boolean) value) ? 1 : 0; + } else { + return 0; + } + } + @Override public final String getString(String tag) { - Object value = this.get(tag); - return value == null ? null : value.toString(); + Object value = this.get(tag); + return value == null ? null : value.toString(); } - + @Override public final TagMap.Entry getEntry(String tag) { - Object value = this.get(tag); - return value == null ? null : TagMap.Entry.newAnyEntry(tag, value); + Object value = this.get(tag); + return value == null ? null : TagMap.Entry.newAnyEntry(tag, value); } - + @Override public void set(String tag, boolean value) { - this.put(tag, Boolean.valueOf(value)); + this.put(tag, Boolean.valueOf(value)); } - + @Override public void set(String tag, CharSequence value) { - this.put(tag, (Object)value); + this.put(tag, (Object) value); } - + @Override public void set(String tag, double value) { - this.put(tag, Double.valueOf(value)); + this.put(tag, Double.valueOf(value)); } - + @Override public void set(String tag, float value) { - this.put(tag, Float.valueOf(value)); + this.put(tag, Float.valueOf(value)); } - + @Override public void set(String tag, int value) { this.put(tag, Integer.valueOf(value)); } - + @Override public void set(String tag, long value) { - this.put(tag, Long.valueOf(value)); + this.put(tag, Long.valueOf(value)); } - + @Override public void set(String tag, Object value) { - this.put(tag, value); + this.put(tag, value); } - + @Override public void set(TagMap.Entry newEntry) { this.put(newEntry.tag(), newEntry.objectValue()); } - + @Override public Object put(String key, Object value) { - this.checkWriteAccess(); - - return super.put(key, value); + this.checkWriteAccess(); + + return super.put(key, value); } - + @Override public void putAll(Map m) { - this.checkWriteAccess(); - - super.putAll(m); + this.checkWriteAccess(); + + super.putAll(m); } - + @Override public void putAll(TagMap that) { - this.putAll((Map)that); + this.putAll((Map) that); } - + @Override public Object remove(Object key) { - this.checkWriteAccess(); - - return super.remove(key); + this.checkWriteAccess(); + + return super.remove(key); } - + @Override public boolean remove(Object key, Object value) { - this.checkWriteAccess(); - - return super.remove(key, value); + this.checkWriteAccess(); + + return super.remove(key, value); } - + @Override public boolean remove(String tag) { - this.checkWriteAccess(); - - return (super.remove((Object)tag) != null); + this.checkWriteAccess(); + + return (super.remove((Object) tag) != null); } - + @Override public Object compute( - String key, BiFunction remappingFunction) - { - this.checkWriteAccess(); - - return super.compute(key, remappingFunction); + String key, BiFunction remappingFunction) { + this.checkWriteAccess(); + + return super.compute(key, remappingFunction); } - + @Override - public Object computeIfAbsent(String key, Function mappingFunction) { - this.checkWriteAccess(); - - return super.computeIfAbsent(key, mappingFunction); + public Object computeIfAbsent( + String key, Function mappingFunction) { + this.checkWriteAccess(); + + return super.computeIfAbsent(key, mappingFunction); } - + @Override - public Object computeIfPresent(String key, - BiFunction remappingFunction) - { - this.checkWriteAccess(); - - return super.computeIfPresent(key, remappingFunction); + public Object computeIfPresent( + String key, BiFunction remappingFunction) { + this.checkWriteAccess(); + + return super.computeIfPresent(key, remappingFunction); } - + @Override public TagMap immutableCopy() { - if ( this.isEmpty() ) { - return LegacyTagMap.EMPTY; - } else { - return this.copy().freeze(); - } + if (this.isEmpty()) { + return LegacyTagMap.EMPTY; + } else { + return this.copy().freeze(); + } } - + @Override public Iterator iterator() { return new IteratorImpl(this); } - + @Override public Stream stream() { - return StreamSupport.stream(this.spliterator(), false); + return StreamSupport.stream(this.spliterator(), false); } - + private final class IteratorImpl implements Iterator { - private Iterator> wrappedIter; - - IteratorImpl(LegacyTagMap legacyMap) { - this.wrappedIter = legacyMap.entrySet().iterator(); - } - - @Override - public final boolean hasNext() { - return this.wrappedIter.hasNext(); - } - - @Override - public final TagMap.Entry next() { - return TagMap.Entry.newAnyEntry(this.wrappedIter.next()); - } + private Iterator> wrappedIter; + + IteratorImpl(LegacyTagMap legacyMap) { + this.wrappedIter = legacyMap.entrySet().iterator(); + } + + @Override + public final boolean hasNext() { + return this.wrappedIter.hasNext(); + } + + @Override + public final TagMap.Entry next() { + return TagMap.Entry.newAnyEntry(this.wrappedIter.next()); + } } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java index b1f6f983955..ef1c01387b3 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapBucketGroupTest.java @@ -82,7 +82,8 @@ public void _replace() { int origHash = origEntry.hash(); int otherHash = otherEntry.hash(); - OptimizedTagMap.BucketGroup group = new OptimizedTagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); + OptimizedTagMap.BucketGroup group = + new OptimizedTagMap.BucketGroup(origHash, origEntry, otherHash, otherEntry); assertContainsDirectly(origEntry, group); assertContainsDirectly(otherEntry, group); @@ -150,7 +151,8 @@ public void groupChaining() { assertFalse(firstGroup._insert(newHash, newEntry)); assertDoesntContainDirectly(newEntry, firstGroup); - OptimizedTagMap.BucketGroup newHeadGroup = new OptimizedTagMap.BucketGroup(newHash, newEntry, firstGroup); + OptimizedTagMap.BucketGroup newHeadGroup = + new OptimizedTagMap.BucketGroup(newHash, newEntry, firstGroup); assertContainsDirectly(newEntry, newHeadGroup); assertSame(firstGroup, newHeadGroup.prev); @@ -302,7 +304,8 @@ static final OptimizedTagMap.BucketGroup fullGroup(int startingIndex) { TagMap.Entry.newObjectEntry(tag(startingIndex + 1), value(startingIndex + 1)); OptimizedTagMap.BucketGroup group = - new OptimizedTagMap.BucketGroup(firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); + new OptimizedTagMap.BucketGroup( + firstEntry.hash(), firstEntry, secondEntry.hash(), secondEntry); for (int offset = 2; offset < OptimizedTagMap.BucketGroup.LEN; ++offset) { TagMap.Entry anotherEntry = TagMap.Entry.newObjectEntry(tag(startingIndex + offset), value(startingIndex + offset)); @@ -311,7 +314,8 @@ static final OptimizedTagMap.BucketGroup fullGroup(int startingIndex) { return group; } - static final OptimizedTagMap.BucketGroup fullGroup(int startingIndex, OptimizedTagMap.BucketGroup prev) { + static final OptimizedTagMap.BucketGroup fullGroup( + int startingIndex, OptimizedTagMap.BucketGroup prev) { OptimizedTagMap.BucketGroup group = fullGroup(startingIndex); group.prev = prev; return group; diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java index f65bfe83f1e..95f429737aa 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapEntryTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import datadog.trace.api.TagMap.Entry; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -15,13 +16,10 @@ import java.util.concurrent.ThreadFactory; import java.util.function.Function; import java.util.function.Supplier; - import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import datadog.trace.api.TagMap.Entry; - /** * Since TagMap.Entry is thread safe and has involves complicated multi-thread type resolution code, * this test uses a different approach to stress ordering different combinations. @@ -62,34 +60,35 @@ public void anyEntry_object() { } @ParameterizedTest - @ValueSource(booleans={false, true}) + @ValueSource(booleans = {false, true}) public void booleanEntry(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", value), - TagMap.Entry.BOOLEAN, - (entry) -> - multiCheck( - checkKey("foo", entry), - checkValue(value, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); + () -> TagMap.Entry.newBooleanEntry("foo", value), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @ParameterizedTest - @ValueSource(booleans= {false, true}) + @ValueSource(booleans = {false, true}) public void booleanEntry_boxed(boolean value) { test( - () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(value)), - TagMap.Entry.BOOLEAN, - (entry) -> multiCheck( - checkKey("foo", entry), - checkValue(value, entry), - checkFalse(entry::isNumericPrimitive), - checkType(TagMap.Entry.BOOLEAN, entry))); + () -> TagMap.Entry.newBooleanEntry("foo", Boolean.valueOf(value)), + TagMap.Entry.BOOLEAN, + (entry) -> + multiCheck( + checkKey("foo", entry), + checkValue(value, entry), + checkFalse(entry::isNumericPrimitive), + checkType(TagMap.Entry.BOOLEAN, entry))); } @ParameterizedTest - @ValueSource(booleans= {false, true}) + @ValueSource(booleans = {false, true}) public void anyEntry_boolean(boolean value) { test( () -> TagMap.Entry.newAnyEntry("foo", Boolean.valueOf(value)), @@ -104,7 +103,7 @@ public void anyEntry_boolean(boolean value) { } @ParameterizedTest - @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void intEntry(int value) { test( () -> TagMap.Entry.newIntEntry("foo", value), @@ -118,7 +117,7 @@ public void intEntry(int value) { } @ParameterizedTest - @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void intEntry_boxed(int value) { test( () -> TagMap.Entry.newIntEntry("foo", Integer.valueOf(value)), @@ -132,7 +131,7 @@ public void intEntry_boxed(int value) { } @ParameterizedTest - @ValueSource(ints= {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) + @ValueSource(ints = {Integer.MIN_VALUE, -256, -128, -1, 0, 1, 128, 256, Integer.MAX_VALUE}) public void anyEntry_int(int value) { test( () -> TagMap.Entry.newAnyEntry("foo", Integer.valueOf(value)), @@ -147,7 +146,22 @@ public void anyEntry_int(int value) { } @ParameterizedTest - @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) public void longEntry(long value) { test( () -> TagMap.Entry.newLongEntry("foo", value), @@ -161,7 +175,22 @@ public void longEntry(long value) { } @ParameterizedTest - @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) public void longEntry_boxed(long value) { test( () -> TagMap.Entry.newLongEntry("foo", Long.valueOf(value)), @@ -175,7 +204,22 @@ public void longEntry_boxed(long value) { } @ParameterizedTest - @ValueSource(longs= {Long.MIN_VALUE, Integer.MIN_VALUE, -1_048_576L, -256L, -128L, -1L, 0L, 1L, 128L, 256L, 1_048_576L, Integer.MAX_VALUE, Long.MAX_VALUE}) + @ValueSource( + longs = { + Long.MIN_VALUE, + Integer.MIN_VALUE, + -1_048_576L, + -256L, + -128L, + -1L, + 0L, + 1L, + 128L, + 256L, + 1_048_576L, + Integer.MAX_VALUE, + Long.MAX_VALUE + }) public void anyEntry_long(long value) { test( () -> TagMap.Entry.newAnyEntry("foo", Long.valueOf(value)), @@ -190,7 +234,7 @@ public void anyEntry_long(long value) { } @ParameterizedTest - @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void floatEntry(float value) { test( () -> TagMap.Entry.newFloatEntry("foo", value), @@ -204,7 +248,7 @@ public void floatEntry(float value) { } @ParameterizedTest - @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void floatEntry_boxed(float value) { test( () -> TagMap.Entry.newFloatEntry("foo", Float.valueOf(value)), @@ -218,7 +262,7 @@ public void floatEntry_boxed(float value) { } @ParameterizedTest - @ValueSource(floats= {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) + @ValueSource(floats = {Float.MIN_VALUE, -1F, 0F, 1F, 2.171828F, 3.1415F, Float.MAX_VALUE}) public void anyEntry_float(float value) { test( () -> TagMap.Entry.newAnyEntry("foo", Float.valueOf(value)), @@ -232,7 +276,8 @@ public void anyEntry_float(float value) { } @ParameterizedTest - @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void doubleEntry(double value) { test( () -> TagMap.Entry.newDoubleEntry("foo", value), @@ -246,7 +291,8 @@ public void doubleEntry(double value) { } @ParameterizedTest - @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void doubleEntry_boxed(double value) { test( () -> TagMap.Entry.newDoubleEntry("foo", Double.valueOf(value)), @@ -260,7 +306,8 @@ public void doubleEntry_boxed(double value) { } @ParameterizedTest - @ValueSource(doubles= {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) + @ValueSource( + doubles = {Double.MIN_VALUE, Float.MIN_VALUE, -1D, 0D, 1D, Math.E, Math.PI, Double.MAX_VALUE}) public void anyEntry_double(double value) { test( () -> TagMap.Entry.newAnyEntry("foo", Double.valueOf(value)), diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java index b8d2b084b1f..6937960f5d5 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapFuzzTest.java @@ -996,7 +996,7 @@ static final void assertMapEquals(Map expected, OptimizedTagMap Object expectedValue = expected.get(actualEntry.tag()); assertEquals(expectedValue, actualEntry.objectValue()); } - + actual.checkIntegrity(); } @@ -1052,7 +1052,7 @@ static final TagMap tagMapOf(String... keysAndValues) { map.set(key, value); } map.checkIntegrity(); - + return map; } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java index 0d10b79ccff..abd2787f03f 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapLedgerTest.java @@ -73,7 +73,7 @@ public void buildMutable() { // just proving that the map is mutable map.set(key(1000), value(1000)); } - + @ParameterizedTest @EnumSource(TagMapType.class) public void buildMutable(TagMapType mapType) { @@ -111,7 +111,7 @@ public void buildImmutable() { assertFrozen(map); } - + @ParameterizedTest @EnumSource(TagMapType.class) public void buildImmutable(TagMapType mapType) { @@ -145,7 +145,7 @@ public void build_empty(TagMapType mapType) { assertTrue(ledger.isDefinitelyEmpty()); assertNotSame(mapType.empty(), ledger.build(mapType.factory)); } - + @Test public void buildImmutable_empty() { TagMap.Ledger ledger = TagMap.ledger(); diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 8fcfcafab8b..207618b20c4 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -14,7 +14,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; - import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -43,7 +42,7 @@ public void booleanEntry(TagMapType mapType) { map.set("bool", false); TagMap.Entry entry = map.getEntry("bool"); - if ( map.isOptimized() ) { + if (map.isOptimized()) { assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); } @@ -58,7 +57,7 @@ public void intEntry(TagMapType mapType) { map.set("int", 42); TagMap.Entry entry = map.getEntry("int"); - if ( map.isOptimized() ) { + if (map.isOptimized()) { assertEquals(TagMap.Entry.INT, entry.rawType); } @@ -73,7 +72,7 @@ public void longEntry(TagMapType mapType) { map.set("long", 42L); TagMap.Entry entry = map.getEntry("long"); - if ( map.isOptimized() ) { + if (map.isOptimized()) { assertEquals(TagMap.Entry.LONG, entry.rawType); } @@ -88,7 +87,7 @@ public void floatEntry(TagMapType mapType) { map.set("float", 3.14F); TagMap.Entry entry = map.getEntry("float"); - if ( map.isOptimized() ) { + if (map.isOptimized()) { assertEquals(TagMap.Entry.FLOAT, entry.rawType); } @@ -103,7 +102,7 @@ public void doubleEntry(TagMapType mapType) { map.set("double", Math.PI); TagMap.Entry entry = map.getEntry("double"); - if ( map.isOptimized() ) { + if (map.isOptimized()) { assertEquals(TagMap.Entry.DOUBLE, entry.rawType); } @@ -125,13 +124,13 @@ public void empty(TagMapType mapType) { @ParameterizedTest @EnumSource(TagMapTypePair.class) public void putAll_empty(TagMapTypePair mapTypePair) { - // TagMap.EMPTY breaks the rules and uses a different size bucket array - // This test is just to verify that the commonly use putAll still works with EMPTY - TagMap newMap = mapTypePair.firstType.create(); - newMap.putAll(mapTypePair.secondType.empty()); - - assertSize(0, newMap); - assertEmpty(newMap); + // TagMap.EMPTY breaks the rules and uses a different size bucket array + // This test is just to verify that the commonly use putAll still works with EMPTY + TagMap newMap = mapTypePair.firstType.create(); + newMap.putAll(mapTypePair.secondType.empty()); + + assertSize(0, newMap); + assertEmpty(newMap); } @ParameterizedTest @@ -171,7 +170,7 @@ public void map_put_replacement(TagMapType mapType) { public void map_remove(TagMapType mapType) { TagMap map = mapType.create(); - Object prev1 = map.remove((Object)"foo"); + Object prev1 = map.remove((Object) "foo"); assertNull(prev1); map.put("foo", "bar"); @@ -179,7 +178,7 @@ public void map_remove(TagMapType mapType) { assertSize(1, map); assertNotEmpty(map); - Object prev2 = map.remove((Object)"foo"); + Object prev2 = map.remove((Object) "foo"); assertEquals("bar", prev2); assertSize(0, map); assertEmpty(map); @@ -234,7 +233,7 @@ public void copyMany(TagMapType mapType) { int size = randomSize(); TagMap orig = createTagMap(mapType, size); assertSize(size, orig); - + TagMap copy = orig.copy(); orig.clear(); // doing this to make sure that copied isn't modified @@ -243,16 +242,16 @@ public void copyMany(TagMapType mapType) { } assertSize(size, copy); } - + @ParameterizedTest @EnumSource(TagMapType.class) public void immutableCopy(TagMapType mapType) { - int size = randomSize(); - TagMap orig = createTagMap(mapType, size); - - TagMap immutableCopy = orig.immutableCopy(); - orig.clear(); // doing this to make sure that copied isn't modified - + int size = randomSize(); + TagMap orig = createTagMap(mapType, size); + + TagMap immutableCopy = orig.immutableCopy(); + orig.clear(); // doing this to make sure that copied isn't modified + for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), immutableCopy); } @@ -281,9 +280,9 @@ public void shareEntry(TagMapTypePair mapTypePair) { TagMap dest = mapTypePair.secondType.create(); dest.set(orig.getEntry("foo")); - + assertEquals(orig.getEntry("foo"), dest.getEntry("foo")); - if ( mapTypePair == TagMapTypePair.BOTH_OPTIMIZED ) { + if (mapTypePair == TagMapTypePair.BOTH_OPTIMIZED) { assertSame(orig.getEntry("foo"), dest.getEntry("foo")); } } @@ -306,9 +305,9 @@ public void putAll_clobberAll(TagMapTypePair mapTypePair) { for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), dest); } - assertSize(size, dest); + assertSize(size, dest); } - + @ParameterizedTest @EnumSource(TagMapTypePair.class) public void putAll_clobberAndExtras(TagMapTypePair mapTypePair) { @@ -319,7 +318,7 @@ public void putAll_clobberAndExtras(TagMapTypePair mapTypePair) { TagMap dest = mapTypePair.secondType.create(); for (int i = size / 2 - 1; i >= 0; --i) { dest.set(key(i), altValue(i)); - } + } // This should clobber all the values in dest dest.putAll(orig); @@ -327,8 +326,8 @@ public void putAll_clobberAndExtras(TagMapTypePair mapTypePair) { for (int i = 0; i < size; ++i) { assertEntry(key(i), value(i), dest); } - - assertSize(size, dest); + + assertSize(size, dest); } @ParameterizedTest @@ -345,7 +344,7 @@ public void removeMany(TagMapType mapType) { assertSize(size, map); for (int i = 0; i < size; ++i) { - Object removedValue = map.remove((Object)key(i)); + Object removedValue = map.remove((Object) key(i)); assertEquals(value(i), removedValue); // not doing exhaustive size checks @@ -602,8 +601,8 @@ static final void assertEntry(String key, String value, TagMap map) { } static final void assertSize(int size, TagMap map) { - if ( map instanceof OptimizedTagMap ) { - assertEquals(size, ((OptimizedTagMap)map).computeSize()); + if (map instanceof OptimizedTagMap) { + assertEquals(size, ((OptimizedTagMap) map).computeSize()); } assertEquals(size, map.size()); @@ -615,16 +614,16 @@ static final void assertSize(int size, TagMap map) { } static final void assertNotEmpty(TagMap map) { - if ( map instanceof OptimizedTagMap ) { - assertFalse(((OptimizedTagMap)map).checkIfEmpty()); - } + if (map instanceof OptimizedTagMap) { + assertFalse(((OptimizedTagMap) map).checkIfEmpty()); + } assertFalse(map.isEmpty()); } static final void assertEmpty(TagMap map) { - if ( map instanceof OptimizedTagMap ) { - assertTrue(((OptimizedTagMap)map).checkIfEmpty()); - } + if (map instanceof OptimizedTagMap) { + assertTrue(((OptimizedTagMap) map).checkIfEmpty()); + } assertTrue(map.isEmpty()); } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapType.java b/internal-api/src/test/java/datadog/trace/api/TagMapType.java index 4cb62ebedcb..a5c07410c48 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapType.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapType.java @@ -5,16 +5,16 @@ public enum TagMapType { LEGACY(new LegacyTagMapFactory()); final TagMapFactory factory; - + TagMapType(TagMapFactory factory) { - this.factory = factory; + this.factory = factory; } - + public final TagMap create() { - return factory.create(); + return factory.create(); } - + public final TagMap empty() { - return factory.empty(); + return factory.empty(); } -} \ No newline at end of file +} diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java b/internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java index 788bf662d78..1b82df3d3a0 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTypePair.java @@ -5,12 +5,12 @@ public enum TagMapTypePair { BOTH_LEGACY(TagMapType.LEGACY, TagMapType.LEGACY), OPTIMIZED_LEGACY(TagMapType.OPTIMIZED, TagMapType.LEGACY), LEGACY_OPTIMIZED(TagMapType.LEGACY, TagMapType.OPTIMIZED); - + public final TagMapType firstType; public final TagMapType secondType; - + TagMapTypePair(TagMapType firstType, TagMapType secondType) { - this.firstType = firstType; - this.secondType = secondType; + this.firstType = firstType; + this.secondType = secondType; } -} \ No newline at end of file +} From ee432c9942a337621067789bbaab613a0c37d907 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 9 May 2025 09:18:06 -0400 Subject: [PATCH 81/84] More tests Adding getAndSet calls to the various *Entry tests in TagMapTest --- .../java/datadog/trace/api/TagMapTest.java | 108 ++++++++++++++---- 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 207618b20c4..1c9b0fb6d7c 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -38,76 +38,136 @@ public void map_put(TagMapType mapType) { @ParameterizedTest @EnumSource(TagMapType.class) public void booleanEntry(TagMapType mapType) { + boolean first = false; + boolean second = true; + TagMap map = mapType.create(); - map.set("bool", false); + map.set("bool", first); - TagMap.Entry entry = map.getEntry("bool"); + TagMap.Entry firstEntry = map.getEntry("bool"); if (map.isOptimized()) { - assertEquals(TagMap.Entry.BOOLEAN, entry.rawType); + assertEquals(TagMap.Entry.BOOLEAN, firstEntry.rawType); } - assertEquals(false, entry.booleanValue()); - assertEquals(false, map.getBoolean("bool")); + assertEquals(first, firstEntry.booleanValue()); + assertEquals(first, map.getBoolean("bool")); + + TagMap.Entry priorEntry = map.getAndSet("bool", second); + if (map.isOptimized()) { + assertSame(priorEntry, firstEntry); + } + assertEquals(first, priorEntry.booleanValue()); + + TagMap.Entry newEntry = map.getEntry("bool"); + assertEquals(second, newEntry.booleanValue()); } @ParameterizedTest @EnumSource(TagMapType.class) public void intEntry(TagMapType mapType) { + int first = 3142; + int second = 2718; + TagMap map = mapType.create(); - map.set("int", 42); + map.set("int", first); - TagMap.Entry entry = map.getEntry("int"); + TagMap.Entry firstEntry = map.getEntry("int"); if (map.isOptimized()) { - assertEquals(TagMap.Entry.INT, entry.rawType); + assertEquals(TagMap.Entry.INT, firstEntry.rawType); } - assertEquals(42, entry.intValue()); - assertEquals(42, map.getInt("int")); + assertEquals(first, firstEntry.intValue()); + assertEquals(first, map.getInt("int")); + + TagMap.Entry priorEntry = map.getAndSet("int", second); + if (map.isOptimized()) { + assertSame(priorEntry, firstEntry); + } + assertEquals(first, priorEntry.intValue()); + + TagMap.Entry newEntry = map.getEntry("int"); + assertEquals(second, newEntry.intValue()); } @ParameterizedTest @EnumSource(TagMapType.class) public void longEntry(TagMapType mapType) { + long first = 3142L; + long second = 2718L; + TagMap map = mapType.create(); - map.set("long", 42L); + map.set("long", first); - TagMap.Entry entry = map.getEntry("long"); + TagMap.Entry firstEntry = map.getEntry("long"); if (map.isOptimized()) { - assertEquals(TagMap.Entry.LONG, entry.rawType); + assertEquals(TagMap.Entry.LONG, firstEntry.rawType); } - assertEquals(42L, entry.longValue()); - assertEquals(42L, map.getLong("long")); + assertEquals(first, firstEntry.longValue()); + assertEquals(first, map.getLong("long")); + + TagMap.Entry priorEntry = map.getAndSet("long", second); + if (map.isOptimized()) { + assertSame(priorEntry, firstEntry); + } + assertEquals(first, priorEntry.longValue()); + + TagMap.Entry newEntry = map.getEntry("long"); + assertEquals(second, newEntry.longValue()); } @ParameterizedTest @EnumSource(TagMapType.class) public void floatEntry(TagMapType mapType) { + float first = 3.14F; + float second = 2.718F; + TagMap map = mapType.create(); - map.set("float", 3.14F); + map.set("float", first); + + TagMap.Entry firstEntry = map.getEntry("float"); + if (map.isOptimized()) { + assertEquals(TagMap.Entry.FLOAT, firstEntry.rawType); + } - TagMap.Entry entry = map.getEntry("float"); + assertEquals(first, firstEntry.floatValue()); + assertEquals(first, map.getFloat("float")); + + TagMap.Entry priorEntry = map.getAndSet("float", second); if (map.isOptimized()) { - assertEquals(TagMap.Entry.FLOAT, entry.rawType); + assertSame(priorEntry, firstEntry); } + assertEquals(first, priorEntry.floatValue()); - assertEquals(3.14F, entry.floatValue()); - assertEquals(3.14F, map.getFloat("float")); + TagMap.Entry newEntry = map.getEntry("float"); + assertEquals(second, newEntry.floatValue()); } @ParameterizedTest @EnumSource(TagMapType.class) public void doubleEntry(TagMapType mapType) { + double first = Math.PI; + double second = Math.E; + TagMap map = mapType.create(); map.set("double", Math.PI); - TagMap.Entry entry = map.getEntry("double"); + TagMap.Entry firstEntry = map.getEntry("double"); + if (map.isOptimized()) { + assertEquals(TagMap.Entry.DOUBLE, firstEntry.rawType); + } + + assertEquals(first, firstEntry.doubleValue()); + assertEquals(first, map.getDouble("double")); + + TagMap.Entry priorEntry = map.getAndSet("double", second); if (map.isOptimized()) { - assertEquals(TagMap.Entry.DOUBLE, entry.rawType); + assertSame(priorEntry, firstEntry); } + assertEquals(first, priorEntry.doubleValue()); - assertEquals(Math.PI, entry.doubleValue()); - assertEquals(Math.PI, map.getDouble("double")); + TagMap.Entry newEntry = map.getEntry("double"); + assertEquals(second, newEntry.doubleValue()); } @ParameterizedTest From 1c0b641f872c3cab20ebf4a88a7c1464d4da2e46 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 9 May 2025 13:55:11 -0400 Subject: [PATCH 82/84] Adding configuration variable Added configuration variable to toggle between the two map implementations --- .../java/datadog/trace/api/config/GeneralConfig.java | 2 ++ .../src/main/java/datadog/trace/api/Config.java | 10 ++++++++++ .../src/main/java/datadog/trace/api/TagMap.java | 5 ++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java index cf0664b9403..b700b9d1337 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/GeneralConfig.java @@ -100,5 +100,7 @@ public final class GeneralConfig { public static final String APM_TRACING_ENABLED = "apm.tracing.enabled"; public static final String JDK_SOCKET_ENABLED = "jdk.socket.enabled"; + public static final String OPTIMIZED_MAP_ENABLED = "optimized.map.enabled"; + private GeneralConfig() {} } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index 6140f449a7d..35d7c9acbc5 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -540,6 +540,7 @@ public static String getHostName() { private final boolean longRunningTraceEnabled; private final long longRunningTraceInitialFlushInterval; private final long longRunningTraceFlushInterval; + private final boolean cassandraKeyspaceStatementExtractionEnabled; private final boolean couchbaseInternalSpansEnabled; private final boolean elasticsearchBodyEnabled; @@ -576,6 +577,8 @@ public static String getHostName() { private final boolean jdkSocketEnabled; + private final boolean optimizedMapEnabled; + // Read order: System Properties -> Env Variables, [-> properties file], [-> default value] private Config() { this(ConfigProvider.createDefault()); @@ -2027,6 +2030,9 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) this.jdkSocketEnabled = configProvider.getBoolean(JDK_SOCKET_ENABLED, true); + this.optimizedMapEnabled = + configProvider.getBoolean(GeneralConfig.OPTIMIZED_MAP_ENABLED, false); + log.debug("New instance: {}", this); } @@ -3653,6 +3659,10 @@ public boolean isJdkSocketEnabled() { return jdkSocketEnabled; } + public boolean isOptimizedMapEnabled() { + return optimizedMapEnabled; + } + /** @return A map of tags to be applied only to the local application root span. */ public TagMap getLocalRootSpanTags() { final Map runtimeTags = getRuntimeTags(); diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 94e527fafdf..0aea71f4f0b 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1056,7 +1056,10 @@ public EntryChange next() { * That will allow all of the calls to create methods to be devirtualized without a guard */ abstract class TagMapFactory { - public static final TagMapFactory INSTANCE = new OptimizedTagMapFactory(); + public static final TagMapFactory INSTANCE = + Config.get().isOptimizedMapEnabled() + ? new OptimizedTagMapFactory() + : new LegacyTagMapFactory(); public abstract MapT create(); From a37b6f8a675dd4485bb7e9f0eb9f39380a7d0697 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Fri, 9 May 2025 14:38:13 -0400 Subject: [PATCH 83/84] Adding getOrDefault methods to TagMap --- .../main/java/datadog/trace/api/TagMap.java | 119 +++++++++++++----- .../java/datadog/trace/api/TagMapTest.java | 15 +++ 2 files changed, 105 insertions(+), 29 deletions(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index 0aea71f4f0b..cb2757666fc 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -123,14 +123,24 @@ public static Ledger ledger(int size) { boolean getBoolean(String tag); + boolean getBooleanOrDefault(String tag, boolean defaultValue); + int getInt(String tag); + int getIntOrDefault(String tag, int defaultValue); + long getLong(String tag); + long getLongOrDefault(String tag, long defaultValue); + float getFloat(String tag); + float getFloatOrDefault(String tag, float defaultValue); + double getDouble(String tag); + double getDoubleOrDefault(String tag, double defaultValue); + /** * Provides the corresponding Entry object - preferable w/ optimized TagMap if the Entry needs to * have its type checked @@ -1162,26 +1172,46 @@ public final String getString(String tag) { } public final boolean getBoolean(String tag) { + return this.getBooleanOrDefault(tag, false); + } + + public final boolean getBooleanOrDefault(String tag, boolean defaultValue) { Entry entry = this.getEntry(tag); - return entry == null ? false : entry.booleanValue(); + return entry == null ? defaultValue : entry.booleanValue(); } public final int getInt(String tag) { + return getIntOrDefault(tag, 0); + } + + public final int getIntOrDefault(String tag, int defaultValue) { Entry entry = this.getEntry(tag); - return entry == null ? 0 : entry.intValue(); + return entry == null ? defaultValue : entry.intValue(); } public final long getLong(String tag) { + return this.getLongOrDefault(tag, 0L); + } + + public final long getLongOrDefault(String tag, long defaultValue) { Entry entry = this.getEntry(tag); - return entry == null ? 0L : entry.longValue(); + return entry == null ? defaultValue : entry.longValue(); } public final float getFloat(String tag) { + return this.getFloatOrDefault(tag, 0F); + } + + public final float getFloatOrDefault(String tag, float defaultValue) { Entry entry = this.getEntry(tag); - return entry == null ? 0F : entry.floatValue(); + return entry == null ? defaultValue : entry.floatValue(); } public final double getDouble(String tag) { + return this.getDoubleOrDefault(tag, 0D); + } + + public final double getDoubleOrDefault(String tag, double defaultValue) { Entry entry = this.getEntry(tag); return entry == null ? 0D : entry.doubleValue(); } @@ -2641,26 +2671,6 @@ public final void forEach( } } - @Override - public final Object getObject(String tag) { - return this.get(tag); - } - - @Override - public final boolean getBoolean(String tag) { - Object result = this.get(tag); - if (result == null) return false; - - if (result instanceof Boolean) { - return (Boolean) result; - } else if (result instanceof Number) { - Number number = (Number) result; - return (number.intValue() != 0); - } else { - return true; - } - } - @Override public final TagMap.Entry getAndSet(String tag, Object value) { Object prior = this.put(tag, value); @@ -2710,55 +2720,106 @@ public final TagMap.Entry getAndRemove(String tag) { } @Override - public final double getDouble(String tag) { + public final Object getObject(String tag) { + return this.get(tag); + } + + @Override + public final boolean getBoolean(String tag) { + return this.getBooleanOrDefault(tag, false); + } + + @Override + public final boolean getBooleanOrDefault(String tag, boolean defaultValue) { + Object result = this.get(tag); + if (result == null) { + return defaultValue; + } else if (result instanceof Boolean) { + return (Boolean) result; + } else if (result instanceof Number) { + Number number = (Number) result; + return (number.intValue() != 0); + } else { + // deliberately doesn't use defaultValue + return true; + } + } + + @Override + public double getDouble(String tag) { + return this.getDoubleOrDefault(tag, 0D); + } + + @Override + public final double getDoubleOrDefault(String tag, double defaultValue) { Object value = this.get(tag); - if (value instanceof Number) { + if (value == null) { + return defaultValue; + } else if (value instanceof Number) { return ((Number) value).doubleValue(); } else if (value instanceof Boolean) { return ((Boolean) value) ? 1D : 0D; } else { + // deliberately doesn't use defaultValue return 0D; } } @Override public final long getLong(String tag) { + return this.getLongOrDefault(tag, 0L); + } + + public final long getLongOrDefault(String tag, long defaultValue) { Object value = this.get(tag); if (value == null) { - return 0L; + return defaultValue; } else if (value instanceof Number) { return ((Number) value).longValue(); } else if (value instanceof Boolean) { return ((Boolean) value) ? 1L : 0L; } else { + // deliberately doesn't use defaultValue return 0L; } } @Override public final float getFloat(String tag) { + return this.getFloatOrDefault(tag, 0F); + } + + @Override + public final float getFloatOrDefault(String tag, float defaultValue) { Object value = this.get(tag); if (value == null) { - return 0F; + return defaultValue; } else if (value instanceof Number) { return ((Number) value).floatValue(); } else if (value instanceof Boolean) { return ((Boolean) value) ? 1F : 0F; } else { + // deliberately doesn't use defaultValue return 0F; } } @Override public final int getInt(String tag) { + return this.getIntOrDefault(tag, 0); + } + + @Override + public final int getIntOrDefault(String tag, int defaultValue) { Object value = this.get(tag); if (value == null) { - return 0; + return defaultValue; } else if (value instanceof Number) { return ((Number) value).intValue(); } else if (value instanceof Boolean) { return ((Boolean) value) ? 1 : 0; } else { + // deliberately doesn't use defaultValue return 0; } } diff --git a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java index 1c9b0fb6d7c..d73d253c431 100644 --- a/internal-api/src/test/java/datadog/trace/api/TagMapTest.java +++ b/internal-api/src/test/java/datadog/trace/api/TagMapTest.java @@ -60,6 +60,9 @@ public void booleanEntry(TagMapType mapType) { TagMap.Entry newEntry = map.getEntry("bool"); assertEquals(second, newEntry.booleanValue()); + + assertEquals(false, map.getBoolean("unset")); + assertEquals(true, map.getBooleanOrDefault("unset", true)); } @ParameterizedTest @@ -87,6 +90,9 @@ public void intEntry(TagMapType mapType) { TagMap.Entry newEntry = map.getEntry("int"); assertEquals(second, newEntry.intValue()); + + assertEquals(0, map.getInt("unset")); + assertEquals(21, map.getIntOrDefault("unset", 21)); } @ParameterizedTest @@ -114,6 +120,9 @@ public void longEntry(TagMapType mapType) { TagMap.Entry newEntry = map.getEntry("long"); assertEquals(second, newEntry.longValue()); + + assertEquals(0L, map.getLong("unset")); + assertEquals(21L, map.getLongOrDefault("unset", 21L)); } @ParameterizedTest @@ -141,6 +150,9 @@ public void floatEntry(TagMapType mapType) { TagMap.Entry newEntry = map.getEntry("float"); assertEquals(second, newEntry.floatValue()); + + assertEquals(0F, map.getFloat("unset")); + assertEquals(2.718F, map.getFloatOrDefault("unset", 2.718F)); } @ParameterizedTest @@ -168,6 +180,9 @@ public void doubleEntry(TagMapType mapType) { TagMap.Entry newEntry = map.getEntry("double"); assertEquals(second, newEntry.doubleValue()); + + assertEquals(0D, map.getDouble("unset")); + assertEquals(2.718D, map.getDoubleOrDefault("unset", 2.718D)); } @ParameterizedTest From b258b90615ad6e48e58df439a4f6c28d641b4812 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Tue, 13 May 2025 14:59:55 -0400 Subject: [PATCH 84/84] Update internal-api/src/main/java/datadog/trace/api/TagMap.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Raphaƫl Vandon --- internal-api/src/main/java/datadog/trace/api/TagMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal-api/src/main/java/datadog/trace/api/TagMap.java b/internal-api/src/main/java/datadog/trace/api/TagMap.java index cb2757666fc..c64ed02afd5 100644 --- a/internal-api/src/main/java/datadog/trace/api/TagMap.java +++ b/internal-api/src/main/java/datadog/trace/api/TagMap.java @@ -1421,7 +1421,7 @@ private final void putAllUnoptimizedMap(Map * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from one TagMap to another * *

        For optimized TagMaps, this method takes advantage of the consistent TagMap layout to - * quickly handle each bucket. And similar to {@link TagMap#putEntry(Entry)} this method shares + * quickly handle each bucket. And similar to {@link TagMap#getAndSet(Entry)} this method shares * Entry objects from the source TagMap */ public final void putAll(TagMap that) {