-
Notifications
You must be signed in to change notification settings - Fork 292
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
Add pending traces report in tracer flares #8053
Changes from 26 commits
39fd9dc
6663a16
0e10cda
6e7fc17
de9165b
23bc65a
bf1763b
907a0aa
c76ade1
5638d0c
a0bd565
0008ad1
b6b7c0d
b3e9048
713ccda
0ee90c1
bc71dcf
60d08e7
60bb162
3382b3d
fbed357
1dee7f3
af57f32
e9eac71
9786fb5
3d0e3f7
b443ddd
3b1c41a
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 |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package datadog.trace.common.writer; | ||
|
||
import com.squareup.moshi.JsonAdapter; | ||
import com.squareup.moshi.Moshi; | ||
import com.squareup.moshi.Types; | ||
import datadog.trace.api.flare.TracerFlare; | ||
import datadog.trace.core.DDSpan; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.zip.ZipOutputStream; | ||
|
||
public class TraceDumpJsonExporter implements Writer { | ||
|
||
private StringBuilder dumpText; | ||
private ZipOutputStream zip; | ||
private static final JsonAdapter<List<DDSpan>> TRACE_ADAPTER = | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
new Moshi.Builder() | ||
.add(DDSpanJsonAdapter.buildFactory(false)) | ||
.build() | ||
.adapter(Types.newParameterizedType(List.class, DDSpan.class)); | ||
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. Any reason we don't use 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. Initially it was because |
||
|
||
public TraceDumpJsonExporter(ZipOutputStream zip) { | ||
this.zip = zip; | ||
dumpText = new StringBuilder(); | ||
} | ||
|
||
@Override | ||
public void write(final List<DDSpan> trace) { | ||
dumpText.append(TRACE_ADAPTER.toJson(trace)); | ||
dumpText.append('\n'); | ||
} | ||
|
||
@Override | ||
public void start() { | ||
// do nothing | ||
} | ||
|
||
@Override | ||
public boolean flush() { | ||
try { | ||
TracerFlare.addText(zip, "pending_traces.txt", dumpText.toString()); | ||
} catch (IOException e) { | ||
// do nothing | ||
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
// do nothing | ||
} | ||
|
||
@Override | ||
public void incrementDropCounts(int spanCount) {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,22 @@ | |
import static datadog.trace.util.AgentThreadFactory.AgentThread.TRACE_MONITOR; | ||
import static datadog.trace.util.AgentThreadFactory.THREAD_JOIN_TIMOUT_MS; | ||
import static datadog.trace.util.AgentThreadFactory.newAgentThread; | ||
import static java.util.Comparator.comparingLong; | ||
|
||
import datadog.communication.ddagent.SharedCommunicationObjects; | ||
import datadog.trace.api.Config; | ||
import datadog.trace.api.flare.TracerFlare; | ||
import datadog.trace.api.time.TimeSource; | ||
import datadog.trace.common.writer.TraceDumpJsonExporter; | ||
import datadog.trace.core.monitor.HealthMetrics; | ||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Comparator; | ||
import java.util.List; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.function.Predicate; | ||
import java.util.zip.ZipOutputStream; | ||
import org.jctools.queues.MessagePassingQueue; | ||
import org.jctools.queues.MpscBlockingConsumerArrayQueue; | ||
import org.slf4j.Logger; | ||
|
@@ -47,13 +56,16 @@ private static class DelayingPendingTraceBuffer extends PendingTraceBuffer { | |
private static final long FORCE_SEND_DELAY_MS = TimeUnit.SECONDS.toMillis(5); | ||
private static final long SEND_DELAY_NS = TimeUnit.MILLISECONDS.toNanos(500); | ||
private static final long SLEEP_TIME_MS = 100; | ||
private static final CommandElement FLUSH_ELEMENT = new CommandElement(); | ||
private static final CommandElement DUMP_ELEMENT = new CommandElement(); | ||
|
||
private final MpscBlockingConsumerArrayQueue<Element> queue; | ||
private final Thread worker; | ||
private final TimeSource timeSource; | ||
|
||
private volatile boolean closed = false; | ||
private final AtomicInteger flushCounter = new AtomicInteger(0); | ||
private final AtomicInteger dumpCounter = new AtomicInteger(0); | ||
|
||
private final LongRunningTracesTracker runningTracesTracker; | ||
|
||
|
@@ -78,6 +90,7 @@ public void enqueue(Element pendingTrace) { | |
|
||
@Override | ||
public void start() { | ||
TracerFlare.addReporter(new TracerDump(this)); | ||
worker.start(); | ||
} | ||
|
||
|
@@ -108,10 +121,10 @@ public void flush() { | |
if (worker.isAlive()) { | ||
int count = flushCounter.get(); | ||
int loop = 1; | ||
boolean signaled = queue.offer(FlushElement.FLUSH_ELEMENT); | ||
boolean signaled = queue.offer(FLUSH_ELEMENT); | ||
while (!closed && !signaled) { | ||
yieldOrSleep(loop++); | ||
signaled = queue.offer(FlushElement.FLUSH_ELEMENT); | ||
signaled = queue.offer(FLUSH_ELEMENT); | ||
} | ||
int newCount = flushCounter.get(); | ||
while (!closed && count >= newCount) { | ||
|
@@ -130,9 +143,28 @@ public void accept(Element pendingTrace) { | |
} | ||
} | ||
|
||
private static final class FlushElement implements Element { | ||
static FlushElement FLUSH_ELEMENT = new FlushElement(); | ||
private static final class DumpDrain | ||
implements MessagePassingQueue.Consumer<Element>, MessagePassingQueue.Supplier<Element> { | ||
private static final DumpDrain DUMP_DRAIN = new DumpDrain(); | ||
private static final List<Element> DATA = new ArrayList<>(); | ||
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. Can you make You could then introduce a method called You might want to consider making the
The main benefit this would bring is that there's a clear line between the writing and reading sides at the point we assign a new collection to 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. I'm not fully sure I understand the point of making |
||
private int index = 0; | ||
|
||
@Override | ||
public void accept(Element pendingTrace) { | ||
DATA.add(pendingTrace); | ||
} | ||
|
||
@Override | ||
public Element get() { | ||
if (index < DATA.size()) { | ||
return DATA.get(index++); | ||
} | ||
return null; // Should never reach here or else queue may break according to | ||
// MessagePassingQueue docs | ||
} | ||
} | ||
|
||
private static final class CommandElement implements Element { | ||
@Override | ||
public long oldestFinishedTime() { | ||
return 0; | ||
|
@@ -180,13 +212,20 @@ public void run() { | |
pendingTrace = queue.take(); // block until available; | ||
} | ||
|
||
if (pendingTrace instanceof FlushElement) { | ||
if (pendingTrace == FLUSH_ELEMENT) { | ||
// Since this is an MPSC queue, the drain needs to be called on the consumer thread | ||
queue.drain(WriteDrain.WRITE_DRAIN); | ||
flushCounter.incrementAndGet(); | ||
continue; | ||
} | ||
|
||
if (pendingTrace == DUMP_ELEMENT) { | ||
queue.drain(DumpDrain.DUMP_DRAIN, 50); | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
queue.fill(DumpDrain.DUMP_DRAIN, DumpDrain.DATA.size()); | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dumpCounter.incrementAndGet(); | ||
continue; | ||
} | ||
|
||
// The element is no longer in the queue | ||
pendingTrace.setEnqueued(false); | ||
|
||
|
@@ -208,7 +247,7 @@ public void run() { | |
// Trace has been unmodified long enough, go ahead and write whatever is finished. | ||
pendingTrace.write(); | ||
} else { | ||
// Trace is too new. Requeue it and sleep to avoid a hot loop. | ||
// Trace is too new. Requeue it and sleep to avoid a hot loop. | ||
enqueue(pendingTrace); | ||
Thread.sleep(SLEEP_TIME_MS); | ||
} | ||
|
@@ -277,4 +316,52 @@ public static PendingTraceBuffer discarding() { | |
public abstract void flush(); | ||
|
||
public abstract void enqueue(Element pendingTrace); | ||
|
||
private static class TracerDump implements TracerFlare.Reporter { | ||
private static final Comparator<Element> TRACE_BY_START_TIME = | ||
comparingLong(trace -> trace.getRootSpan().getStartTime()); | ||
private static final Predicate<Element> NOT_PENDING_TRACE = | ||
element -> !(element instanceof PendingTrace); | ||
private final DelayingPendingTraceBuffer buffer; | ||
|
||
private TracerDump(DelayingPendingTraceBuffer buffer) { | ||
this.buffer = buffer; | ||
} | ||
|
||
@Override | ||
public void prepareForFlare() { | ||
if (buffer.worker.isAlive()) { | ||
int count = buffer.dumpCounter.get(); | ||
int loop = 1; | ||
boolean signaled = buffer.queue.offer(DelayingPendingTraceBuffer.DUMP_ELEMENT); | ||
while (!buffer.closed && !signaled) { | ||
buffer.yieldOrSleep(loop++); | ||
signaled = buffer.queue.offer(DelayingPendingTraceBuffer.DUMP_ELEMENT); | ||
} | ||
int newCount = buffer.dumpCounter.get(); | ||
while (!buffer.closed && count >= newCount) { | ||
buffer.yieldOrSleep(loop++); | ||
newCount = buffer.dumpCounter.get(); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void addReportToFlare(ZipOutputStream zip) throws IOException { | ||
DelayingPendingTraceBuffer.DumpDrain.DATA.removeIf(NOT_PENDING_TRACE); | ||
// Storing oldest traces first | ||
DelayingPendingTraceBuffer.DumpDrain.DATA.sort((TRACE_BY_START_TIME).reversed()); | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
TraceDumpJsonExporter writer = new TraceDumpJsonExporter(zip); | ||
for (Element e : DelayingPendingTraceBuffer.DumpDrain.DATA) { | ||
if (e instanceof PendingTrace) { | ||
PendingTrace trace = (PendingTrace) e; | ||
writer.write(new ArrayList<>(trace.getSpans())); | ||
mhlidd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
// Releasing memory used for ArrayList in drain | ||
DelayingPendingTraceBuffer.DumpDrain.DATA.clear(); | ||
writer.flush(); | ||
} | ||
} | ||
} |
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.
🟠 Code Quality Violation
StringBuilder can lead to memory leaks in long lasting classes (...read more)
StringBuffers and StringBuilders have the potential to grow significantly, which could lead to memory leaks if they are retained within objects with extended lifetimes.