-
Notifications
You must be signed in to change notification settings - Fork 329
Avoid ArrayList copying from TraceInterceptors #10828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8a33be4
e092c44
c2e108a
139625f
e7e438c
9894ce5
135a03d
f709474
a17de74
58cdbbf
c9da48a
456e9ee
e6a612f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1211,15 +1211,15 @@ public AgentDataStreamsMonitoring getDataStreamsMonitoring() { | |
| return dataStreamsMonitoring; | ||
| } | ||
|
|
||
| private final RatelimitedLogger rlLog = new RatelimitedLogger(log, 1, MINUTES); | ||
| private static final RatelimitedLogger rlLog = new RatelimitedLogger(log, 1, MINUTES); | ||
|
|
||
| /** | ||
| * We use the sampler to know if the trace has to be reported/written. The sampler is called on | ||
| * the first span (root span) of the trace. If the trace is marked as a sample, we report it. | ||
| * | ||
| * @param trace a list of the spans related to the same trace | ||
| */ | ||
| void write(final List<DDSpan> trace) { | ||
| void write(final SpanList trace) { | ||
| if (trace.isEmpty() || !trace.get(0).traceConfig().isTraceEnabled()) { | ||
| return; | ||
| } | ||
|
|
@@ -1257,30 +1257,59 @@ void write(final List<DDSpan> trace) { | |
| } | ||
| } | ||
|
|
||
| private List<DDSpan> interceptCompleteTrace(List<DDSpan> trace) { | ||
| if (!interceptors.isEmpty() && !trace.isEmpty()) { | ||
| Collection<? extends MutableSpan> interceptedTrace = new ArrayList<>(trace); | ||
| for (final TraceInterceptor interceptor : interceptors) { | ||
| try { | ||
| // If one TraceInterceptor throws an exception, then continue with the next one | ||
| interceptedTrace = interceptor.onTraceComplete(interceptedTrace); | ||
| } catch (Throwable e) { | ||
| String interceptorName = interceptor.getClass().getName(); | ||
| rlLog.warn("Throwable raised in TraceInterceptor {}", interceptorName, e); | ||
| } | ||
| if (interceptedTrace == null) { | ||
| interceptedTrace = emptyList(); | ||
| } | ||
| private List<DDSpan> interceptCompleteTrace(SpanList originalTrace) { | ||
| return interceptCompleteTrace(interceptors, originalTrace); | ||
| } | ||
|
|
||
| static final List<DDSpan> interceptCompleteTrace( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Introduced for ease of testing |
||
| SortedSet<TraceInterceptor> interceptors, SpanList originalTrace) { | ||
| if (interceptors.isEmpty()) { | ||
| return originalTrace; | ||
| } | ||
| if (originalTrace.isEmpty()) { | ||
| return SpanList.EMPTY; | ||
| } | ||
|
|
||
| // Using TraceList to optimize the common case where the interceptors, | ||
| // don't alter the list. If the interceptors just return the provided | ||
| // List, then no need to copy to another List. | ||
|
|
||
| // As an extra precaution, also check the modCount before and after on | ||
| // the TraceList, since TraceInterceptor could put some other type of | ||
| // object into the List. | ||
|
|
||
| // There is still a risk that a TraceInterceptor holds onto the provided | ||
| // List and modifies it later on, but we cannot safeguard against | ||
| // every possible misuse. | ||
| Collection<? extends MutableSpan> interceptedTrace = originalTrace; | ||
| int originalModCount = originalTrace.modCount(); | ||
|
|
||
| for (final TraceInterceptor interceptor : interceptors) { | ||
| try { | ||
| // If one TraceInterceptor throws an exception, then continue with the next one | ||
| interceptedTrace = interceptor.onTraceComplete(interceptedTrace); | ||
| } catch (Throwable e) { | ||
| String interceptorName = interceptor.getClass().getName(); | ||
| rlLog.warn("Throwable raised in TraceInterceptor {}", interceptorName, e); | ||
| } | ||
| if (interceptedTrace == null) { | ||
| interceptedTrace = emptyList(); | ||
| } | ||
| } | ||
|
|
||
| trace = new ArrayList<>(interceptedTrace.size()); | ||
| if (interceptedTrace == null || interceptedTrace.isEmpty()) { | ||
| return SpanList.EMPTY; | ||
| } else if (interceptedTrace == originalTrace && originalTrace.modCount() == originalModCount) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly, mod count checking is probably overkill, but I'm erring on the side of caution.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the risks of removing the size check? Afterall it will be a different object if != 1 so I would have simplified perhaps
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My main thinking was that it reduces the lifecycle of the empty list by substituting it with the canonical empty list. Honestly, I don't think it makes much difference either way. |
||
| return originalTrace; | ||
| } else { | ||
| SpanList trace = new SpanList(interceptedTrace.size()); | ||
| for (final MutableSpan span : interceptedTrace) { | ||
| if (span instanceof DDSpan) { | ||
| trace.add((DDSpan) span); | ||
| } | ||
| } | ||
| return trace; | ||
| } | ||
| return trace; | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package datadog.trace.core; | ||
|
|
||
| import java.util.ArrayList; | ||
|
|
||
| /** ArrayList that exposes modCount to allow for an optimization in TraceInterceptor handling */ | ||
| final class SpanList extends ArrayList<DDSpan> { | ||
| static final SpanList EMPTY = new SpanList(0); | ||
|
|
||
| /** | ||
| * Convenience function for creating a SpanList containing a single DDSpan. Meant as replacement | ||
| * for Collections.singletonList when creating a SpanList. | ||
| * | ||
| * @param span != null | ||
| * @return a SpanList | ||
| */ | ||
| static final SpanList of(DDSpan span) { | ||
| SpanList list = new SpanList(1); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This factory method appears optimized for handling single-span traces, as reflected by its growth pattern (1 → 2 → 3 → 4 → 6 → 9 → 13 → …). This is generally acceptable since traces are not expected to grow significantly when passing through interceptors, and in cases with multiple spans, the trace size is typically known and used to initialize the list appropriately. That said, it might be worth documenting this assumption in the method’s Javadoc for future maintainability and clarity.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it isn't really intended to be used as a generic factory -- mostly just as an easy substitute for singletonList. That said, I'm happy to add some Javadoc to it. |
||
| list.add(span); | ||
| return list; | ||
| } | ||
|
|
||
| /** | ||
| * Constructs a SpanList with the specified capacity | ||
| * | ||
| * @param capacity | ||
| */ | ||
| SpanList(int capacity) { | ||
| super(capacity); | ||
| } | ||
|
|
||
| /** The modifcation count of the List - can be used to check if the List has been altered */ | ||
| int modCount() { | ||
| return this.modCount; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this wasn't static before. I made it static, so I could call it from the static version of interceptCompleteTrace.