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 05c213a9c18..08db9c6205f 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 @@ -1083,24 +1083,22 @@ static final ReusableSingleSpanBuilder reuseSingleSpanBuilder( @Override public AgentSpan startSpan(final String instrumentationName, final CharSequence spanName) { - return singleSpanBuilder(instrumentationName, spanName).start(); + return CoreSpanBuilder.startSpan( + this, instrumentationName, spanName, null, false, CoreSpanBuilder.DEFAULT_TIMESTAMP_MICRO); } @Override public AgentSpan startSpan( final String instrumentationName, final CharSequence spanName, final long startTimeMicros) { - return singleSpanBuilder(instrumentationName, spanName) - .withStartTimestamp(startTimeMicros) - .start(); + return CoreSpanBuilder.startSpan( + this, instrumentationName, spanName, null, false, startTimeMicros); } @Override public AgentSpan startSpan( String instrumentationName, final CharSequence spanName, final AgentSpanContext parent) { - return singleSpanBuilder(instrumentationName, spanName) - .ignoreActiveSpan() - .asChildOf(parent) - .start(); + return CoreSpanBuilder.startSpan( + this, instrumentationName, spanName, parent, true, CoreSpanBuilder.DEFAULT_TIMESTAMP_MICRO); } @Override @@ -1109,11 +1107,8 @@ public AgentSpan startSpan( final CharSequence spanName, final AgentSpanContext parent, final long startTimeMicros) { - return buildSpan(instrumentationName, spanName) - .ignoreActiveSpan() - .asChildOf(parent) - .withStartTimestamp(startTimeMicros) - .start(); + return CoreSpanBuilder.startSpan( + this, instrumentationName, spanName, parent, true, startTimeMicros); } @Override @@ -1521,6 +1516,10 @@ private static Map invertMap(Map map) { /** Spans are built using this builder */ public abstract static class CoreSpanBuilder implements AgentTracer.SpanBuilder { + protected static final boolean IGNORE_SCOPE_DEFAULT = false; + protected static final int SPAN_ID_DEFAULT = 0; + protected static final long DEFAULT_TIMESTAMP_MICRO = 0L; + protected final CoreTracer tracer; protected String instrumentationName; @@ -1535,12 +1534,12 @@ public abstract static class CoreSpanBuilder implements AgentTracer.SpanBuilder protected String resourceName; protected boolean errorFlag; protected CharSequence spanType; - protected boolean ignoreScope = false; + protected boolean ignoreScope = IGNORE_SCOPE_DEFAULT; protected Object builderRequestContextDataAppSec; protected Object builderRequestContextDataIast; protected Object builderCiVisibilityContextData; protected List links; - protected long spanId; + protected long spanId = SPAN_ID_DEFAULT; // Make sure any fields added here are also reset properly in ReusableSingleSpanBuilder.reset CoreSpanBuilder(CoreTracer tracer) { @@ -1553,8 +1552,51 @@ public final CoreSpanBuilder ignoreActiveSpan() { return this; } - protected final DDSpan buildSpan() { - DDSpan span = DDSpan.create(instrumentationName, timestampMicro, buildSpanContext(), links); + protected static final DDSpan buildSpan( + final CoreTracer tracer, + long spanId, + String instrumentationName, + long timestampMicro, + String serviceName, + CharSequence operationName, + String resourceName, + AgentSpanContext resolvedParentContext, + boolean ignoreScope, + boolean errorFlag, + CharSequence spanType, + TagMap.Ledger tagLedger, + List links, + Object builderRequestContextDataAppSec, + Object builderRequestContextDataIast, + Object builderCiVisibilityContextData) { + return buildSpanImpl( + tracer, + instrumentationName, + timestampMicro, + links, + buildSpanContext( + tracer, + spanId, + serviceName, + operationName, + resourceName, + resolvedParentContext, + errorFlag, + spanType, + tagLedger, + links, + builderRequestContextDataAppSec, + builderRequestContextDataIast, + builderCiVisibilityContextData)); + } + + protected static final DDSpan buildSpanImpl( + CoreTracer tracer, + String instrumentationName, + long timestampMicro, + List links, + DDSpanContext spanContext) { + DDSpan span = DDSpan.create(instrumentationName, timestampMicro, spanContext, links); if (span.isLocalRootSpan()) { EndpointTracker tracker = tracer.onRootSpanStarted(span); if (tracker != null) { @@ -1564,7 +1606,8 @@ protected final DDSpan buildSpan() { return span; } - private final void addParentContextAsLinks(AgentSpanContext parentContext) { + private static final List addParentContextLink( + List links, AgentSpanContext parentContext) { SpanLink link; if (parentContext instanceof ExtractedContext) { String headers = ((ExtractedContext) parentContext).getPropagationStyle().toString(); @@ -1577,38 +1620,154 @@ private final void addParentContextAsLinks(AgentSpanContext parentContext) { } else { link = SpanLink.from(parentContext); } - withLink(link); + return addLink(links, link); } - private final void addTerminatedContextAsLinks() { - if (this.parent instanceof TagContext) { + protected static final List addTerminatedContextAsLinks( + List links, AgentSpanContext parentContext) { + if (parentContext instanceof TagContext) { List terminatedContextLinks = - ((TagContext) this.parent).getTerminatedContextLinks(); + ((TagContext) parentContext).getTerminatedContextLinks(); if (!terminatedContextLinks.isEmpty()) { - if (this.links == null) { - this.links = new ArrayList<>(); - } - this.links.addAll(terminatedContextLinks); + return addLinks(links, terminatedContextLinks); } } + return links; + } + + protected static final List addLink( + List links, AgentSpanLink link) { + if (links == null) links = new ArrayList<>(); + links.add(link); + return links; + } + + protected static final List addLinks( + List links, List additionalLinks) { + if (links == null) { + links = new ArrayList<>(additionalLinks); + } else { + links.addAll(additionalLinks); + } + return links; } @Override public abstract AgentSpan start(); protected AgentSpan startImpl() { - AgentSpanContext pc = parent; - if (pc == null && !ignoreScope) { - final AgentSpan span = tracer.activeSpan(); - if (span != null) { - pc = span.context(); + return startSpan( + this.tracer, + this.spanId, + this.instrumentationName, + this.timestampMicro, + this.serviceName, + this.operationName, + this.resourceName, + this.parent, + this.ignoreScope, + this.errorFlag, + this.spanType, + this.tagLedger, + this.links, + this.builderRequestContextDataAppSec, + this.builderRequestContextDataIast, + this.builderCiVisibilityContextData); + } + + protected static final AgentSpan startSpan( + final CoreTracer tracer, + String instrumentationName, + CharSequence operationName, + AgentSpanContext specifiedParentContext, + boolean ignoreScope, + long timestampMicros) { + return startSpan( + tracer, + SPAN_ID_DEFAULT, + instrumentationName, + timestampMicros, + null /* serviceName */, + operationName, + null /* resourceName */, + specifiedParentContext, + ignoreScope, + false /* errorFlag */, + null /* spanType */, + null /* tagLedger */, + null /* links */, + null /* appSec */, + null /* iast */, + null /* ciViz */); + } + + protected static final AgentSpan startSpan( + final CoreTracer tracer, + long spanId, + String instrumentationName, + long timestampMicro, + String serviceName, + CharSequence operationName, + String resourceName, + AgentSpanContext specifiedParentContext, + boolean ignoreScope, + boolean errorFlag, + CharSequence spanType, + TagMap.Ledger tagLedger, + List links, + Object builderRequestContextDataAppSec, + Object builderRequestContextDataIast, + Object builderCiVisibilityContextData) { + // Find the parent context + AgentSpanContext parentContext = specifiedParentContext; + if (parentContext == null && !ignoreScope) { + // use the Scope as parent unless overridden or ignored. + final AgentSpan activeSpan = tracer.scopeManager.activeSpan(); + if (activeSpan != null) { + parentContext = activeSpan.context(); } } - if (pc == BlackHoleSpan.Context.INSTANCE) { - return new BlackHoleSpan(pc.getTraceId()); + if (parentContext == BlackHoleSpan.Context.INSTANCE) { + return new BlackHoleSpan(parentContext.getTraceId()); + } + + // Handle remote terminated context as span links + if (parentContext != null && parentContext.isRemote()) { + switch (Config.get().getTracePropagationBehaviorExtract()) { + case RESTART: + links = addParentContextLink(links, parentContext); + parentContext = null; + break; + + case IGNORE: + parentContext = null; + break; + + case CONTINUE: + default: + links = addTerminatedContextAsLinks(links, specifiedParentContext); + break; + } } - return buildSpan(); + + return buildSpan( + tracer, + spanId, + instrumentationName, + timestampMicro, + serviceName, + operationName, + resourceName, + parentContext, + ignoreScope, + errorFlag, + spanType, + tagLedger, + links, + builderRequestContextDataAppSec, + builderRequestContextDataIast, + builderCiVisibilityContextData); } @Override @@ -1733,10 +1892,22 @@ public final CoreSpanBuilder withSpanId(final long spanId) { * * @return the context */ - private final DDSpanContext buildSpanContext() { - final DDTraceId traceId; - final long spanId; - final long parentSpanId; + protected static final DDSpanContext buildSpanContext( + final CoreTracer tracer, + long spanId, + String serviceName, + CharSequence operationName, + String resourceName, + AgentSpanContext resolvedParentContext, + boolean errorFlag, + CharSequence spanType, + TagMap.Ledger tagLedger, + List links, + Object builderRequestContextDataAppSec, + Object builderRequestContextDataIast, + Object builderCiVisibilityContextData) { + DDTraceId traceId; + long parentSpanId; final Map baggage; final Baggage w3cBaggage; final TraceCollector parentTraceCollector; @@ -1753,43 +1924,16 @@ private final DDSpanContext buildSpanContext() { final PathwayContext pathwayContext; final PropagationTags propagationTags; - if (this.spanId == 0) { + if (spanId == 0) { spanId = tracer.idGenerationStrategy.generateSpanId(); - } else { - spanId = this.spanId; - } - - // Find the parent context - AgentSpanContext parentContext = parent; - if (parentContext == null && !ignoreScope) { - // use the Scope as parent unless overridden or ignored. - final AgentSpan activeSpan = tracer.scopeManager.activeSpan(); - if (activeSpan != null) { - parentContext = activeSpan.context(); - } - } - // Handle remote terminated context as span links - if (parentContext != null && parentContext.isRemote()) { - switch (Config.get().getTracePropagationBehaviorExtract()) { - case RESTART: - addParentContextAsLinks(parentContext); - parentContext = null; - break; - case IGNORE: - parentContext = null; - break; - case CONTINUE: - default: - addTerminatedContextAsLinks(); - } } String parentServiceName = null; // Propagate internal trace. // Note: if we are not in the context of distributed tracing, and we are starting the first // root span, parentContext will be null at this point. - if (parentContext instanceof DDSpanContext) { - final DDSpanContext ddsc = (DDSpanContext) parentContext; + if (resolvedParentContext instanceof DDSpanContext) { + final DDSpanContext ddsc = (DDSpanContext) resolvedParentContext; traceId = ddsc.getTraceId(); parentSpanId = ddsc.getSpanId(); baggage = ddsc.getBaggageItems(); @@ -1805,7 +1949,7 @@ private final DDSpanContext buildSpanContext() { if (serviceName == null) { serviceName = parentServiceName; } - RequestContext requestContext = ((DDSpanContext) parentContext).getRequestContext(); + RequestContext requestContext = ((DDSpanContext) resolvedParentContext).getRequestContext(); if (requestContext != null) { requestContextDataAppSec = requestContext.getData(RequestContextSlot.APPSEC); requestContextDataIast = requestContext.getData(RequestContextSlot.IAST); @@ -1819,21 +1963,21 @@ private final DDSpanContext buildSpanContext() { } else { long endToEndStartTime; - if (parentContext instanceof ExtractedContext) { + if (resolvedParentContext instanceof ExtractedContext) { // Propagate external trace - final ExtractedContext extractedContext = (ExtractedContext) parentContext; + final ExtractedContext extractedContext = (ExtractedContext) resolvedParentContext; traceId = extractedContext.getTraceId(); parentSpanId = extractedContext.getSpanId(); samplingPriority = extractedContext.getSamplingPriority(); endToEndStartTime = extractedContext.getEndToEndStartTime(); propagationTags = extractedContext.getPropagationTags(); - } else if (parentContext != null) { + } else if (resolvedParentContext != null) { traceId = - parentContext.getTraceId() == DDTraceId.ZERO + resolvedParentContext.getTraceId() == DDTraceId.ZERO ? tracer.idGenerationStrategy.generateTraceId() - : parentContext.getTraceId(); - parentSpanId = parentContext.getSpanId(); - samplingPriority = parentContext.getSamplingPriority(); + : resolvedParentContext.getTraceId(); + parentSpanId = resolvedParentContext.getSpanId(); + samplingPriority = resolvedParentContext.getSamplingPriority(); endToEndStartTime = 0; propagationTags = tracer.propagationTagsFactory.empty(); } else { @@ -1848,8 +1992,8 @@ private final DDSpanContext buildSpanContext() { ConfigSnapshot traceConfig; // Get header tags and set origin whether propagating or not. - if (parentContext instanceof TagContext) { - TagContext tc = (TagContext) parentContext; + if (resolvedParentContext instanceof TagContext) { + TagContext tc = (TagContext) resolvedParentContext; traceConfig = (ConfigSnapshot) tc.getTraceConfig(); coreTags = tc.getTags(); coreTagsNeedsIntercept = true; // maybe intercept isn't needed? @@ -1885,10 +2029,10 @@ private final DDSpanContext buildSpanContext() { // Use parent pathwayContext if present and started pathwayContext = - parentContext != null - && parentContext.getPathwayContext() != null - && parentContext.getPathwayContext().isStarted() - ? parentContext.getPathwayContext() + resolvedParentContext != null + && resolvedParentContext.getPathwayContext() != null + && resolvedParentContext.getPathwayContext().isStarted() + ? resolvedParentContext.getPathwayContext() : tracer.dataStreamsMonitoring.newPathwayContext(); // when removing fake services the best upward service name to pick is the local root one @@ -1920,8 +2064,7 @@ private final DDSpanContext buildSpanContext() { serviceName = tracer.serviceName; } - final CharSequence operationName = - this.operationName != null ? this.operationName : resourceName; + if (operationName == null) operationName = resourceName; final TagMap mergedTracerTags = traceConfig.mergedTracerTags; boolean mergedTracerTagsNeedsIntercept = traceConfig.mergedTracerTagsNeedsIntercept; diff --git a/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java b/dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java index 4c404ae0a38..a8b1683ae6c 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 @@ -76,6 +76,8 @@ public class DDSpanContext /** The collection of all span related to this one */ private final TraceCollector traceCollector; + private final TagInterceptor tagInterceptor; + /** Baggage is associated with the whole trace and shared with other spans */ private volatile Map baggageItems; @@ -327,6 +329,7 @@ public DDSpanContext( assert traceCollector != null; this.traceCollector = traceCollector; + this.tagInterceptor = this.traceCollector.getTracer().getTagInterceptor(); assert traceId != null; this.traceId = traceId; @@ -761,9 +764,122 @@ public void setTag(final String tag, final Object value) { synchronized (unsafeTags) { unsafeTags.remove(tag); } - } else if (!traceCollector.getTracer().getTagInterceptor().interceptTag(this, tag, value)) { + } else if (!tagInterceptor.interceptTag(this, tag, value)) { + synchronized (unsafeTags) { + unsafeTags.set(tag, value); + } + } + } + + public void setTag(final String tag, final String value) { + if (null == tag) { + return; + } + if (null == value) { + synchronized (unsafeTags) { + unsafeTags.remove(tag); + } + } else if (!tagInterceptor.interceptTag(this, tag, value)) { + synchronized (unsafeTags) { + unsafeTags.set(tag, value); + } + } + } + + /* + * Uses to determine if there's an opportunity to avoid primitve boxing. + * If the underlying map doesn't support efficient primitives, then boxing is used. + * If the tag may be intercepted, then boxing is also used. + */ + private boolean precheckIntercept(String tag) { + // Usually only a single instanceof TagMap will be loaded, + // so isOptimized is turned into a direct call and then inlines to a constant + // Since isOptimized just returns a constant - doesn't require synchronization + return !unsafeTags.isOptimized() || tagInterceptor.needsIntercept(tag); + } + + /* + * Used when precheckIntercept determines that boxing is unavoidable + * + * Either because the tagInterceptor needs to be fully checked (which requires boxing) + * In that case, a box has already been created so it makes sense to pass the box + * onto TagMap, since optimized TagMap will cache the box + * + * -- OR -- + * + * The TagMap isn't optimized and will need to box the primitive regardless of + * tag interception + */ + private boolean setBox(String tag, Object box) { + if (tagInterceptor.interceptTag(this, tag, box)) { + synchronized (unsafeTags) { + unsafeTags.set(tag, box); + } + } + } + + public void setTag(final String tag, final boolean value) { + if (null == tag) { + return; + } + if ( precheckIntercept(tag) ) { + this.setBox(tag, value); + } else { + synchronized (unsafeTags) { + unsafeTags.set(tag, value); + } + } + } + + public void setTag(final String tag, final int value) { + if (null == tag) { + return; + } + if ( precheckIntercept(tag) ) { + this.setBox(tag, value); + } else { + synchronized (unsafeTags) { + unsafeTags.set(tag, value); + } + } + } + + public void setTag(final String tag, final long value) { + if (null == tag) { + return; + } + // check needsIntercept first to avoid unnecessary boxing + boolean intercepted = + tagInterceptor.needsIntercept(tag) && tagInterceptor.interceptTag(this, tag, value); + if (!intercepted) { + synchronized (unsafeTags) { + unsafeTags.set(tag, value); + } + } + } + + public void setTag(final String tag, final long value) { + if (null == tag) { + return; + } + if ( precheckIntercept(tag) ) { + this.setBox(tag, value); + } else { + synchronized (unsafeTags) { + unsafeTags.set(tag, value); + } + } + } + + public void setTag(final String tag, final double value) { + if (null == tag) { + return; + } + if ( precheckIntercept(tag) ) { + this.setBox(tag, value); + } else { synchronized (unsafeTags) { - unsafeSetTag(tag, value); + unsafeTags.set(tag, value); } } } @@ -784,12 +900,11 @@ void setAllTags(final TagMap map, boolean needsIntercept) { // to avoid using a capturing lambda map.forEach( this, - traceCollector.getTracer().getTagInterceptor(), - (ctx, tagInterceptor, tagEntry) -> { + (ctx, tagEntry) -> { String tag = tagEntry.tag(); Object value = tagEntry.objectValue(); - if (!tagInterceptor.interceptTag(ctx, tag, value)) { + if (!ctx.tagInterceptor.interceptTag(ctx, tag, value)) { ctx.unsafeTags.set(tagEntry); } }); @@ -804,7 +919,6 @@ void setAllTags(final TagMap.Ledger ledger) { return; } - TagInterceptor tagInterceptor = traceCollector.getTracer().getTagInterceptor(); synchronized (unsafeTags) { for (final TagMap.EntryChange entryChange : ledger) { if (entryChange.isRemoval()) { @@ -829,7 +943,6 @@ void setAllTags(final Map map) { } 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())) { @@ -841,7 +954,19 @@ void setAllTags(final Map map) { } void unsafeSetTag(final String tag, final Object value) { - unsafeTags.put(tag, value); + unsafeTags.set(tag, value); + } + + void unsafeSetTag(final String tag, final CharSequence value) { + unsafeTags.set(tag, value); + } + + void unsafeSetTag(final String tag, final byte value) { + unsafeTags.set(tag, value); + } + + void unsafeSetTag(final String tag, final double value) { + unsafeTags.set(tag, value); } Object getTag(final String key) {