Skip to content

Commit c55fb32

Browse files
dougqhdevflow.devflow-routing-intake
andauthored
Avoid creation of empty CopyOnWriteArrayList for span links (#10822)
Avoid creation of empty CopyOnWriteArrayList for span links This change shares an "EMPTY" list between span when there are no span links. This avoids the construction of a CopyOnWriteArrayList, empty Object[], and internal lock Object for the common case where there are no links. This elimination of the CopyOnWriteArrayList in the empty case, reduced allocation by 5% and GC time by 7% in a span creation stress test. Merge branch 'master' into dougqh/span-links-allocation Introducing WritableSpanLinks & SpanLinkAccessor Introduced the new interface WritableSpanLinks & SpanLinkAccessor that are used to provide limited access to DDSpan Updated TagProcessors to use the new interfaces Updated tests as needed Merge branch 'dougqh/span-links-allocation' of github.com:DataDog/dd-trace-java into dougqh/span-links-allocation spotless Addressing review comments - removing implied visibility from new interfaces - added some javadoc to new interfaces - renamed WritableSpanLinks -> AppendableSpanLinks Remove vestigial processTags(Map, DDSpanContext, List) method from TagsPostProcessor Removed vestigial processTags(Map, DDSpanContext, List) method from TagsPostProcessor This method was a hold over from before TagMap was introduced. It was kept to make porting tests easier while TagMap was still in experimental phase. The method has outlived its usefulness; however, removing the method did require updating a lot of tests. spotless codeNarcTest - fixing typo Merge branch 'master' into dougqh/span-links-allocation Restricting API access Removed SpanLinkAccessor interface SpanLinkAccessor was intended to limit how the span is modified in DDSpanContext#processTagsAndBaggage, but the trade-off was introducing a public getLinks method on DDSpan. Reduced visibility of processTagsAndBaggage methods on DDSpanContext Since processTagsAndBaggage is really just a helper routine of DDSpan.processTagsAndBaggage and the usage is a bit sensitive, I decided to reduce the visibility spotless Merge branch 'master' into dougqh/span-links-allocation Co-authored-by: devflow.devflow-routing-intake <devflow.devflow-routing-intake@kubernetes.us1.ddbuild.io>
1 parent 39f8353 commit c55fb32

24 files changed

+261
-142
lines changed

dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ static DDSpan create(
116116
*/
117117
private volatile int longRunningVersion = 0;
118118

119-
protected final List<AgentSpanLink> links;
119+
private static final List<AgentSpanLink> EMPTY = Collections.emptyList();
120+
protected volatile List<AgentSpanLink> links;
120121

121122
/**
122123
* Spans should be constructed using the builder, not by calling the constructor directly.
@@ -144,7 +145,7 @@ private DDSpan(
144145
context.getTraceCollector().touch(); // external clock: explicitly update lastReferenced
145146
}
146147

147-
this.links = links == null ? new CopyOnWriteArrayList<>() : new CopyOnWriteArrayList<>(links);
148+
this.links = links == null || links.isEmpty() ? EMPTY : new CopyOnWriteArrayList<>(links);
148149
}
149150

150151
public boolean isFinished() {
@@ -764,12 +765,12 @@ public CharSequence getType() {
764765

765766
@Override
766767
public void processServiceTags() {
767-
context.earlyProcessTags(links);
768+
context.earlyProcessTags(this);
768769
}
769770

770771
@Override
771772
public void processTagsAndBaggage(final MetadataConsumer consumer) {
772-
context.processTagsAndBaggage(consumer, longRunningVersion, links);
773+
context.processTagsAndBaggage(consumer, longRunningVersion, this);
773774
}
774775

775776
@Override
@@ -883,10 +884,44 @@ public TraceConfig traceConfig() {
883884
return context.getTraceCollector().getTraceConfig();
884885
}
885886

887+
List<? extends AgentSpanLink> getLinks() {
888+
return this.links;
889+
}
890+
886891
@Override
887892
public void addLink(AgentSpanLink link) {
888-
if (link != null) {
889-
this.links.add(link);
893+
if (link == null) {
894+
return;
895+
}
896+
897+
// If links are initially null / empty, then the shared placeholder List EMPTY is used.
898+
// Bacause EMPTY is shared, EMPTY is safe for reading, but not for writing.
899+
// On write - if links is the EMPTY placeholder, then need to create a CopyOnWriteArrayList
900+
// owned by this DDSpan
901+
902+
// Creation of the CopyOnWriteArrayList is done via double checking locking using volatile &
903+
// synchronized
904+
905+
// If before or inside the synchronized block, links no longer points to EMPTY,
906+
// then this thread or another thread has already handled the list construction,
907+
// so just add to the list
908+
909+
// If links still points to EMPTY inside the synchronized block, then construct a new
910+
// CopyOnWriteArrayList containing the newly added link
911+
912+
List<AgentSpanLink> links = this.links;
913+
if (links != EMPTY) {
914+
links.add(link);
915+
return;
916+
}
917+
918+
synchronized (this) {
919+
links = this.links;
920+
if (links != EMPTY) {
921+
links.add(link);
922+
} else {
923+
this.links = new CopyOnWriteArrayList<>(Collections.singletonList(link));
924+
}
890925
}
891926
}
892927

dd-trace-core/src/main/java/datadog/trace/core/DDSpanContext.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import datadog.trace.api.sampling.PrioritySampling;
2525
import datadog.trace.api.sampling.SamplingMechanism;
2626
import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext;
27-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
27+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
2828
import datadog.trace.bootstrap.instrumentation.api.Baggage;
2929
import datadog.trace.bootstrap.instrumentation.api.ProfilerContext;
3030
import datadog.trace.bootstrap.instrumentation.api.ProfilingContextIntegration;
@@ -1087,19 +1087,24 @@ public <T> void setMetaStruct(final String field, final T value) {
10871087
}
10881088
}
10891089

1090-
public void earlyProcessTags(List<AgentSpanLink> links) {
1090+
void earlyProcessTags(AppendableSpanLinks links) {
10911091
synchronized (unsafeTags) {
10921092
TagsPostProcessorFactory.eagerProcessor().processTags(unsafeTags, this, links);
10931093
}
10941094
}
10951095

1096-
public void processTagsAndBaggage(
1097-
final MetadataConsumer consumer, int longRunningVersion, List<AgentSpanLink> links) {
1096+
void processTagsAndBaggage(
1097+
final MetadataConsumer consumer, int longRunningVersion, DDSpan restrictedSpan) {
1098+
// NOTE: The span is passed for the sole purpose of allowing updating & reading of the span
1099+
// links
1100+
// This is a compromise to avoid...
1101+
// - creating an extra wrapper object that would create significant allocation
1102+
// - implementing an interface to read the spans that require making the read method public
10981103
synchronized (unsafeTags) {
10991104
// Tags
1100-
TagsPostProcessorFactory.lazyProcessor().processTags(unsafeTags, this, links);
1105+
TagsPostProcessorFactory.lazyProcessor().processTags(unsafeTags, this, restrictedSpan);
11011106

1102-
String linksTag = DDSpanLink.toTag(links);
1107+
String linksTag = DDSpanLink.toTag(restrictedSpan.getLinks());
11031108
if (linksTag != null) {
11041109
unsafeTags.put(SPAN_LINKS, linksTag);
11051110
}

dd-trace-core/src/main/java/datadog/trace/core/DDSpanLink.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static SpanLink from(ExtractedContext context, SpanAttributes attributes)
6969
* @param links The span link collection to encode.
7070
* @return The encoded tag value, {@code null} if no links.
7171
*/
72-
public static String toTag(List<AgentSpanLink> links) {
72+
public static String toTag(List<? extends AgentSpanLink> links) {
7373
if (links == null || links.isEmpty()) {
7474
return null;
7575
}

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/HttpEndpointPostProcessor.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66

77
import datadog.trace.api.TagMap;
88
import datadog.trace.api.endpoint.EndpointResolver;
9-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
9+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
1010
import datadog.trace.core.DDSpanContext;
11-
import java.util.List;
1211
import org.slf4j.Logger;
1312
import org.slf4j.LoggerFactory;
1413

@@ -56,7 +55,7 @@ public HttpEndpointPostProcessor() {
5655

5756
@Override
5857
public void processTags(
59-
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
58+
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
6059
if (!endpointResolver.isEnabled()) {
6160
log.debug("EndpointResolver is not enabled, skipping HTTP endpoint post processing");
6261
return;

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/IntegrationAdder.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
import static datadog.trace.api.DDTags.DD_INTEGRATION;
44

55
import datadog.trace.api.TagMap;
6-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
6+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
77
import datadog.trace.core.DDSpanContext;
8-
import java.util.List;
98

109
public class IntegrationAdder extends TagsPostProcessor {
1110
@Override
1211
public void processTags(
13-
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
12+
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
1413
final CharSequence instrumentationName = spanContext.getIntegrationName();
1514
if (instrumentationName != null) {
1615
unsafeTags.set(DD_INTEGRATION, instrumentationName);

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/InternalTagsAdder.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
import datadog.trace.api.DDTags;
66
import datadog.trace.api.TagMap;
7-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
7+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
88
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
99
import datadog.trace.core.DDSpanContext;
10-
import java.util.List;
1110
import javax.annotation.Nullable;
1211

1312
public final class InternalTagsAdder extends TagsPostProcessor {
@@ -21,7 +20,7 @@ public InternalTagsAdder(@Nullable final String ddService, @Nullable final Strin
2120

2221
@Override
2322
public void processTags(
24-
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
23+
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
2524
if (spanContext == null || ddService == null) {
2625
return;
2726
}

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PayloadTagsProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import datadog.trace.api.ConfigDefaults;
88
import datadog.trace.api.TagMap;
99
import datadog.trace.api.telemetry.LogCollector;
10-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
10+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
1111
import datadog.trace.core.DDSpanContext;
1212
import datadog.trace.core.util.JsonStreamParser;
1313
import datadog.trace.payloadtags.PayloadTagsData;
@@ -72,7 +72,7 @@ public static PayloadTagsProcessor create(Config config) {
7272

7373
@Override
7474
public void processTags(
75-
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
75+
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
7676
int spanMaxTags = maxTags + unsafeTags.size();
7777
for (Map.Entry<String, RedactionRules> tagPrefixRedactionRules :
7878
redactionRulesByTagPrefix.entrySet()) {

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PeerServiceCalculator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import datadog.trace.api.TagMap;
66
import datadog.trace.api.naming.NamingSchema;
77
import datadog.trace.api.naming.SpanNaming;
8-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
8+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
99
import datadog.trace.bootstrap.instrumentation.api.Tags;
1010
import datadog.trace.core.DDSpanContext;
11-
import java.util.List;
1211
import java.util.Map;
1312
import javax.annotation.Nonnull;
1413

@@ -34,7 +33,7 @@ public PeerServiceCalculator() {
3433

3534
@Override
3635
public void processTags(
37-
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
36+
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
3837
Object peerService = unsafeTags.getObject(Tags.PEER_SERVICE);
3938
// the user set it
4039
if (peerService != null) {

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/PostProcessorChain.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package datadog.trace.core.tagprocessor;
22

33
import datadog.trace.api.TagMap;
4-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
4+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
55
import datadog.trace.core.DDSpanContext;
6-
import java.util.List;
76
import java.util.Objects;
87
import javax.annotation.Nonnull;
98

@@ -16,7 +15,7 @@ public PostProcessorChain(@Nonnull final TagsPostProcessor... processors) {
1615

1716
@Override
1817
public void processTags(
19-
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
18+
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
2019
for (final TagsPostProcessor tagsPostProcessor : chain) {
2120
tagsPostProcessor.processTags(unsafeTags, spanContext, spanLinks);
2221
}

dd-trace-core/src/main/java/datadog/trace/core/tagprocessor/QueryObfuscator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
import com.google.re2j.PatternSyntaxException;
66
import datadog.trace.api.DDTags;
77
import datadog.trace.api.TagMap;
8-
import datadog.trace.bootstrap.instrumentation.api.AgentSpanLink;
8+
import datadog.trace.bootstrap.instrumentation.api.AppendableSpanLinks;
99
import datadog.trace.bootstrap.instrumentation.api.Tags;
1010
import datadog.trace.core.DDSpanContext;
1111
import datadog.trace.util.Strings;
12-
import java.util.List;
1312
import org.slf4j.Logger;
1413
import org.slf4j.LoggerFactory;
1514

@@ -59,7 +58,7 @@ private String obfuscate(String query) {
5958

6059
@Override
6160
public void processTags(
62-
TagMap unsafeTags, DDSpanContext spanContext, List<AgentSpanLink> spanLinks) {
61+
TagMap unsafeTags, DDSpanContext spanContext, AppendableSpanLinks spanLinks) {
6362
Object query = unsafeTags.getObject(DDTags.HTTP_QUERY);
6463
if (query instanceof CharSequence) {
6564
query = obfuscate(query.toString());

0 commit comments

Comments
 (0)