From cc99a817d36f7b220d81ff33188f2d09cf42efcf Mon Sep 17 00:00:00 2001 From: jean-philippe bempel Date: Mon, 21 Jul 2025 14:47:11 +0200 Subject: [PATCH] Add capping on SourceFile tracking queue tracks number of item put into the queue and dequeued. limits to 4096 items. Avoid using queue.size that is O(n) and keep the size in AtomicInteger dump the number of entries in the SourceFile map into tracer flare --- .../datadog/debugger/agent/DebuggerAgent.java | 4 ++- .../agent/SourceFileTrackingTransformer.java | 25 +++++++++++++++++-- .../SourceFileTrackingTransformerTest.java | 17 +++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java index f18771de9c7..4de8622576a 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java @@ -478,7 +478,9 @@ private static void addReportToFlare(ZipOutputStream zip) throws IOException { "SymbolDB stats:", symbolDBStats, "Exception Fingerprints:", - exceptionFingerprints); + exceptionFingerprints, + "SourceFile tracking entries:", + String.valueOf(classesToRetransformFinder.getClassNamesBySourceFile().size())); TracerFlare.addText(zip, "dynamic_instrumentation.txt", content); } } diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/SourceFileTrackingTransformer.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/SourceFileTrackingTransformer.java index 1ca248f9dd5..43925c3469b 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/SourceFileTrackingTransformer.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/SourceFileTrackingTransformer.java @@ -6,6 +6,7 @@ import com.datadog.debugger.util.ClassFileHelper; import com.datadog.debugger.util.ClassNameFiltering; import datadog.trace.api.Config; +import datadog.trace.relocate.api.RatelimitedLogger; import datadog.trace.util.AgentTaskScheduler; import datadog.trace.util.Strings; import java.lang.instrument.ClassFileTransformer; @@ -14,6 +15,7 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,10 +26,15 @@ */ public class SourceFileTrackingTransformer implements ClassFileTransformer { private static final Logger LOGGER = LoggerFactory.getLogger(SourceFileTrackingTransformer.class); + private static final int MINUTES_BETWEEN_ERROR_LOG = 5; + static final int MAX_QUEUE_SIZE = 4096; + private final RatelimitedLogger ratelimitedLogger = + new RatelimitedLogger(LOGGER, MINUTES_BETWEEN_ERROR_LOG, TimeUnit.MINUTES); private final ClassesToRetransformFinder finder; private final Queue queue = new ConcurrentLinkedQueue<>(); private final AgentTaskScheduler scheduler = AgentTaskScheduler.INSTANCE; + private final AtomicInteger queueSize = new AtomicInteger(0); private AgentTaskScheduler.Scheduled scheduled; // this field MUST only be used in flush() calling thread private ClassNameFiltering classNameFilter; @@ -55,14 +62,23 @@ void flush() { if (queue.isEmpty()) { return; } - int size = queue.size(); + int itemCount = 0; long start = System.nanoTime(); SourceFileItem item; while ((item = queue.poll()) != null) { + queueSize.decrementAndGet(); registerSourceFile(item.className, item.classfileBuffer); + itemCount++; } LOGGER.debug( - "flushing {} source file items in {}ms", size, (System.nanoTime() - start) / 1_000_000); + "flushing {} source file items in {}ms, totalentries: {}", + itemCount, + (System.nanoTime() - start) / 1_000_000, + finder.getClassNamesBySourceFile().size()); + } + + int getQueueSize() { + return queueSize.get(); } @Override @@ -76,7 +92,12 @@ public byte[] transform( if (className == null) { return null; } + if (queueSize.get() >= MAX_QUEUE_SIZE) { + ratelimitedLogger.warn("SourceFile Tracking queue full, dropping class: {}", className); + return null; + } queue.add(new SourceFileItem(className, classfileBuffer)); + queueSize.incrementAndGet(); return null; } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SourceFileTrackingTransformerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SourceFileTrackingTransformerTest.java index 056e3897543..d6ceea59d4a 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SourceFileTrackingTransformerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/SourceFileTrackingTransformerTest.java @@ -1,5 +1,6 @@ package com.datadog.debugger.agent; +import static com.datadog.debugger.agent.SourceFileTrackingTransformer.MAX_QUEUE_SIZE; import static org.junit.jupiter.api.Assertions.assertEquals; import static utils.TestClassFileHelper.getClassFileBytes; @@ -138,6 +139,22 @@ void thirdPartyExcludes() throws IllegalClassFormatException { assertEquals(0, finder.getClassNamesBySourceFile().size()); } + @Test + void maxQueue() throws IllegalClassFormatException { + ClassesToRetransformFinder finder = new ClassesToRetransformFinder(); + SourceFileTrackingTransformer sourceFileTrackingTransformer = + new SourceFileTrackingTransformer(finder); + for (int i = 0; i < MAX_QUEUE_SIZE + 10; i++) { + sourceFileTrackingTransformer.transform( + null, + getInternalName(TopLevelHelper.class), + null, + null, + getClassFileBytes(TopLevelHelper.class)); + } + assertEquals(MAX_QUEUE_SIZE, sourceFileTrackingTransformer.getQueueSize()); + } + private static void replaceInByteArray(byte[] buffer, byte[] oldBytes, byte[] newBytes) { int oldIdx = 0; for (int i = 0; i < buffer.length; i++) {