Skip to content

Commit 8a16316

Browse files
committed
fix review issues
Add weakReference for original classes map Add try/catch on retransform call in removeAllProbes Handle null class (newly loaded) based on the name for re-transformation Use AgentTaskScheduler for scheduling poll Fix tests
1 parent 8067519 commit 8a16316

File tree

12 files changed

+93
-36
lines changed

12 files changed

+93
-36
lines changed

dd-java-agent/agent-debugging/src/main/java/com/datadog/debugging/agent/DebuggerProbeRedefinition.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.datadog.debugging.probes.ProbesPoller;
44
import java.lang.instrument.Instrumentation;
55
import java.lang.instrument.UnmodifiableClassException;
6+
import java.lang.ref.WeakReference;
67
import java.util.ArrayList;
78
import java.util.HashMap;
89
import java.util.HashSet;
@@ -81,23 +82,40 @@ public void accept(DebuggerProbe[] debuggerProbes) {
8182
}
8283
}
8384

84-
private void removeAllProbes() throws UnmodifiableClassException {
85+
private void removeAllProbes() {
8586
if (currentTransformer == null) {
8687
return;
8788
}
8889
log.info("Remove probes by restoring & re-transforming original class definitions...");
89-
Map<String, Class<?>> originalClasses = currentTransformer.getOriginalClasses();
90+
Map<String, WeakReference<Class<?>>> originalClasses = currentTransformer.getOriginalClasses();
9091
instrumentation.removeTransformer(currentTransformer);
91-
for (Map.Entry<String, Class<?>> entry : originalClasses.entrySet()) {
92-
Class<?> clazz = entry.getValue();
92+
Set<String> newlyLoadedClassNames = new HashSet<>();
93+
for (Map.Entry<String, WeakReference<Class<?>>> entry : originalClasses.entrySet()) {
94+
Class<?> clazz = entry.getValue().get();
9395
if (clazz != null) {
94-
log.info("Restoring class {}", clazz.getName());
95-
instrumentation.retransformClasses(clazz);
96+
restoreClass(clazz);
9697
} else {
97-
log.warn("class null for: {} cannot re-transform", entry.getKey());
98+
newlyLoadedClassNames.add(entry.getKey());
99+
}
100+
}
101+
// process nealy loaded class (no Class instance at the time of transform) based on the name
102+
if (!newlyLoadedClassNames.isEmpty()) {
103+
for (Class<?> clazz : instrumentation.getAllLoadedClasses()) {
104+
if (newlyLoadedClassNames.contains(clazz.getName())) {
105+
restoreClass(clazz);
106+
}
98107
}
99108
}
100109
probeIds.clear();
101110
currentTransformer = null;
102111
}
112+
113+
private void restoreClass(Class<?> clazz) {
114+
log.info("Restoring class {}", clazz.getName());
115+
try {
116+
instrumentation.retransformClasses(clazz);
117+
} catch (UnmodifiableClassException ex) {
118+
log.warn("Cannot re-transform class {}:", clazz.getName(), ex);
119+
}
120+
}
103121
}

dd-java-agent/agent-debugging/src/main/java/com/datadog/debugging/agent/DebuggerTransformer.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import datadog.trace.bootstrap.debugging.DebuggerContext;
88
import java.io.IOException;
99
import java.lang.instrument.ClassFileTransformer;
10+
import java.lang.ref.WeakReference;
1011
import java.nio.file.Files;
1112
import java.nio.file.Path;
1213
import java.nio.file.Paths;
@@ -27,14 +28,14 @@
2728
public class DebuggerTransformer implements ClassFileTransformer {
2829
private final DebuggerProbe[] probes;
2930
private final Config config;
30-
private final Map<String, Class<?>> originalClasses = new HashMap<>();
31+
private final Map<String, WeakReference<Class<?>>> originalClasses = new HashMap<>();
3132

3233
public DebuggerTransformer(Config config, DebuggerProbe... probes) {
3334
this.probes = probes;
3435
this.config = config;
3536
}
3637

37-
public Map<String, Class<?>> getOriginalClasses() {
38+
public Map<String, WeakReference<Class<?>>> getOriginalClasses() {
3839
return originalClasses;
3940
}
4041

@@ -62,7 +63,7 @@ public byte[] transform(
6263
String typeName = className.replace('/', '.');
6364
if (where.isTypeMatching(typeName)) {
6465
log.debug("Matched type '{}'", typeName);
65-
originalClasses.put(className, classBeingRedefined);
66+
originalClasses.put(className, new WeakReference<>(classBeingRedefined));
6667
ClassReader reader = new ClassReader(classfileBuffer);
6768
dumpOriginalClassFile(className, classfileBuffer);
6869
ClassNode classNode = new ClassNode();

dd-java-agent/agent-debugging/src/main/java/com/datadog/debugging/agent/DebuggingAgent.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
import com.datadog.debugging.probes.ProbesPoller;
44
import datadog.trace.api.Config;
55
import datadog.trace.bootstrap.debugging.DebuggerContext;
6-
import java.io.FileInputStream;
7-
import java.io.IOException;
8-
import java.io.InputStream;
96
import java.lang.instrument.Instrumentation;
107
import lombok.extern.slf4j.Slf4j;
118

dd-java-agent/agent-debugging/src/main/java/com/datadog/debugging/probes/ProbesPoller.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import datadog.trace.api.Config;
77
import datadog.trace.api.RatelimitedLogger;
8+
import datadog.trace.util.AgentTaskScheduler;
89
import java.io.FileInputStream;
910
import java.io.IOException;
1011
import java.io.InputStream;
@@ -26,17 +27,16 @@
2627
import okhttp3.ResponseBody;
2728

2829
@Slf4j
29-
public class ProbesPoller {
30+
public class ProbesPoller implements AgentTaskScheduler.Target<ProbesPoller> {
3031
private static final long NANOSECONDS_BETWEEN_ERROR_LOG = TimeUnit.MINUTES.toNanos(5);
3132
private static final int MAX_RUNNING_REQUESTS = 1;
3233

3334
private final ScheduledExecutorService okHttpExecutorService;
3435
private final OkHttpClient client;
35-
private final String url;
3636
private final RatelimitedLogger ratelimitedLogger;
3737
private final ProbeRedefinitionListener listener;
38-
private ScheduledFuture<?> future;
3938
private final Request request;
39+
private final long pollInterval;
4040

4141
public interface ProbeRedefinitionListener {
4242
void accept(DebuggerProbe[] debuggerProbes);
@@ -48,12 +48,14 @@ public ProbesPoller(Config config, ProbeRedefinitionListener listener) {
4848

4949
ProbesPoller(
5050
Config config, ProbeRedefinitionListener listener, RatelimitedLogger ratelimitedLogger) {
51-
url = config.getDebuggingProbeUrl();
51+
String url = config.getDebuggingProbeUrl();
5252
if (url == null || url.length() == 0) {
5353
throw new IllegalArgumentException("Probe url is empty");
5454
}
5555
this.listener = listener;
5656
this.ratelimitedLogger = ratelimitedLogger;
57+
// TODO add a jitter to avoid herd issue
58+
this.pollInterval = Duration.ofSeconds(config.getDebuggingPollInterval()).toMillis();
5759
log.debug("Started Probes Poller with target url {}", url);
5860
// This is the same thing OkHttp Dispatcher is doing except thread naming and daemonization
5961
okHttpExecutorService =
@@ -86,16 +88,17 @@ public ProbesPoller(Config config, ProbeRedefinitionListener listener) {
8688
request = new Request.Builder().url(url).get().build();
8789
}
8890

89-
public void start() {
90-
future =
91-
okHttpExecutorService.scheduleAtFixedRate(this::pollDebuggerProbes, 0, 1, TimeUnit.SECONDS);
91+
@Override
92+
public ProbesPoller get() {
93+
return this;
9294
}
9395

94-
public void stop() {
95-
future.cancel(true);
96+
public void start() {
97+
AgentTaskScheduler.INSTANCE.scheduleAtFixedRate(
98+
this::pollDebuggerProbes, this, 0, pollInterval, TimeUnit.MILLISECONDS);
9699
}
97100

98-
void pollDebuggerProbes() {
101+
void pollDebuggerProbes(AgentTaskScheduler.Target<ProbesPoller> target) {
99102
try {
100103
try (Response response = client.newCall(request).execute()) {
101104
ResponseBody body = response.body();

dd-java-agent/agent-debugging/src/main/java/com/datadog/debugging/uploader/SnapshotUploader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import java.util.concurrent.ThreadPoolExecutor;
1414
import java.util.concurrent.TimeUnit;
1515
import java.util.concurrent.TimeoutException;
16-
1716
import lombok.extern.slf4j.Slf4j;
1817
import okhttp3.Call;
1918
import okhttp3.Callback;
@@ -37,6 +36,7 @@ public enum Kind {
3736
DIAGNOSTIC("diagnostic");
3837

3938
private String apiTarget;
39+
4040
Kind(String apiTarget) {
4141
this.apiTarget = apiTarget;
4242
}
@@ -72,7 +72,7 @@ public SnapshotUploader(Config config) {
7272

7373
// Visible for testing
7474
SnapshotUploader(Config config, RatelimitedLogger ratelimitedLogger) {
75-
url = config.getDebuggingSnapshotUrl();
75+
String url = config.getDebuggingSnapshotUrl();
7676
if (url == null || url.length() == 0) {
7777
throw new IllegalArgumentException("Snapshot url is empty");
7878
}

dd-java-agent/agent-debugging/src/test/java/com/datadog/debugging/agent/DebuggerProbeRedefinitionTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import java.lang.instrument.Instrumentation;
1313
import java.lang.instrument.UnmodifiableClassException;
14+
import java.lang.ref.WeakReference;
1415
import java.util.HashMap;
1516
import java.util.Map;
1617
import org.junit.jupiter.api.Test;
@@ -64,15 +65,15 @@ private static DebuggerTransformer createTransformer(DebuggerProbe[] debuggerPro
6465
private static DebuggerTransformer createTransformerWithOrigClasses(
6566
DebuggerProbe[] debuggerProbes) {
6667
DebuggerTransformer transformer = mock(DebuggerTransformer.class);
67-
Map<String, Class<?>> origClasses = new HashMap<>();
68+
Map<String, WeakReference<Class<?>>> origClasses = new HashMap<>();
6869
for (DebuggerProbe probe : debuggerProbes) {
6970
Class<?> clazz = null;
7071
try {
7172
clazz = Class.forName(probe.getWhere().getTypeName());
7273
} catch (ClassNotFoundException e) {
7374
e.printStackTrace();
7475
}
75-
origClasses.put(clazz.getName(), clazz);
76+
origClasses.put(clazz.getName(), new WeakReference<>(clazz));
7677
}
7778
when(transformer.getOriginalClasses()).thenReturn(origClasses);
7879
return transformer;
Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,65 @@
11
package com.datadog.debugging.agent;
22

3+
import static org.mockito.ArgumentMatchers.eq;
34
import static org.mockito.ArgumentMatchers.matches;
45
import static org.mockito.Mockito.verify;
56
import static org.mockito.Mockito.when;
67

78
import com.datadog.debugging.uploader.SnapshotUploader;
89
import datadog.trace.api.Config;
10+
import datadog.trace.bootstrap.debugging.DiagnosticMessage;
911
import datadog.trace.bootstrap.debugging.Snapshot;
12+
import java.util.Arrays;
1013
import org.junit.jupiter.api.Test;
1114
import org.junit.jupiter.api.extension.ExtendWith;
1215
import org.mockito.Mock;
1316
import org.mockito.junit.jupiter.MockitoExtension;
1417

1518
@ExtendWith(MockitoExtension.class)
1619
public class SnapshotSinkTest {
17-
private static final String URL_PATH = "/lalala";
1820
private static final String PROBE_ID = "12fd-8490-c111-4374-ffde";
1921

2022
@Mock private Config config;
2123
@Mock private SnapshotUploader snapshotUploader;
2224

2325
@Test
24-
public void add() {
26+
public void addSnapshot() {
2527
when(config.getDebuggingUploadFormat()).thenReturn("json");
2628
SnapshotSink sink = new SnapshotSink(config, snapshotUploader);
2729
Snapshot snapshot = new Snapshot(Thread.currentThread(), PROBE_ID);
2830
sink.addSnapshot(snapshot);
2931
String regex =
3032
"\\{\"startTs\":\\d+,\"timestamp\":\\d+,\"probeId\":\"12fd-8490-c111-4374-ffde\",\"language\":\"java\",\"thread\":\\{\"id\":\\d+,\"name\":\"Test worker\"\\},\"version\":0,\"captures\":\\[\\],\"duration\":0\\}";
31-
verify(snapshotUploader).upload(matches(regex), SnapshotUploader.Kind.SNAPSHOT);
33+
verify(snapshotUploader).upload(matches(regex), eq(SnapshotUploader.Kind.SNAPSHOT));
3234
}
3335

3436
@Test
35-
public void addBinary() {
37+
public void addSnapshotBinary() {
3638
when(config.getDebuggingUploadFormat()).thenReturn("binary");
3739
SnapshotSink sink = new SnapshotSink(config, snapshotUploader);
3840
Snapshot snapshot = new Snapshot(Thread.currentThread(), PROBE_ID);
3941
sink.addSnapshot(snapshot);
4042
// FIXME when binary format is implemented and used
4143
}
44+
45+
@Test
46+
public void addDiagnostics() {
47+
when(config.getDebuggingUploadFormat()).thenReturn("json");
48+
SnapshotSink sink = new SnapshotSink(config, snapshotUploader);
49+
sink.addDiagnostics(
50+
PROBE_ID, Arrays.asList(new DiagnosticMessage(DiagnosticMessage.Kind.ERROR, "error1")));
51+
String regex =
52+
"\\{\"probeId\":\"12fd-8490-c111-4374-ffde\",\"messages\":\\[\\{\"timestamp\":\\d+,\"kind\":\"ERROR\",\"message\":\"error1\"\\}\\]\\}";
53+
verify(snapshotUploader).upload(matches(regex), eq(SnapshotUploader.Kind.DIAGNOSTIC));
54+
}
55+
56+
@Test
57+
public void addDiagnosticsBinary() {
58+
when(config.getDebuggingUploadFormat()).thenReturn("binary");
59+
SnapshotSink sink = new SnapshotSink(config, snapshotUploader);
60+
Snapshot snapshot = new Snapshot(Thread.currentThread(), PROBE_ID);
61+
sink.addDiagnostics(
62+
PROBE_ID, Arrays.asList(new DiagnosticMessage(DiagnosticMessage.Kind.ERROR, "error1")));
63+
// FIXME when binary format is implemented and used
64+
}
4265
}

dd-java-agent/agent-debugging/src/test/java/com/datadog/debugging/probes/ProbesPollerTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ public void receive200Response() {
4949
new MockResponse().setResponseCode(200).setBody(getResourceContent("/test_probe.json")));
5050
when(config.getDebuggingProbeUrl()).thenReturn(server.url(URL_PATH).toString());
5151
ProbesPoller poller = new ProbesPoller(config, ProbesPollerTest::assertGoodProbeDefinition);
52-
poller.pollDebuggerProbes();
52+
poller.pollDebuggerProbes(null);
5353
}
5454

5555
@Test
5656
public void receive500Response() {
5757
server.enqueue(new MockResponse().setResponseCode(500));
5858
when(config.getDebuggingProbeUrl()).thenReturn(server.url(URL_PATH).toString());
5959
ProbesPoller poller = new ProbesPoller(config, ProbesPollerTest::assertGoodProbeDefinition);
60-
poller.pollDebuggerProbes();
60+
poller.pollDebuggerProbes(null);
6161
}
6262

6363
@Test
@@ -69,7 +69,7 @@ public void receive500ResponseWithBody() {
6969
.setBody("{\"message\": \"error\"}"));
7070
when(config.getDebuggingProbeUrl()).thenReturn(server.url(URL_PATH).toString());
7171
ProbesPoller poller = new ProbesPoller(config, ProbesPollerTest::assertGoodProbeDefinition);
72-
poller.pollDebuggerProbes();
72+
poller.pollDebuggerProbes(null);
7373
}
7474

7575
@Test

dd-java-agent/agent-debugging/src/test/java/com/datadog/debugging/uploader/SnapshotUploaderTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@ void testDiagnostics() throws Exception {
120120

121121
RecordedRequest request = server.takeRequest(5, TimeUnit.SECONDS);
122122
assertNotNull(request);
123-
assertTrue(request.getRequestUrl().toString().endsWith("/" + SnapshotUploader.Kind.DIAGNOSTIC.getApiTarget()));
123+
assertTrue(
124+
request
125+
.getRequestUrl()
126+
.toString()
127+
.endsWith("/" + SnapshotUploader.Kind.DIAGNOSTIC.getApiTarget()));
124128
assertTrue(request.getBody().size() > 0);
125129
}
126130

@@ -142,7 +146,10 @@ public void testConnectionRefused() throws IOException, InterruptedException {
142146
// Shutting down uploader ensures all callbacks are called on http client
143147
uploader.shutdown();
144148
verify(ratelimitedLogger)
145-
.warn(eq("Failed to upload snapshot to {}"), ArgumentMatchers.argThat(arg -> arg.toString().startsWith(url.toString())), any(ConnectException.class));
149+
.warn(
150+
eq("Failed to upload snapshot to {}"),
151+
ArgumentMatchers.argThat(arg -> arg.toString().startsWith(url.toString())),
152+
any(ConnectException.class));
146153
}
147154

148155
@Test

dd-trace-api/src/main/java/datadog/trace/api/ConfigDefaults.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public final class ConfigDefaults {
6767
static final int DEFAULT_DEBUGGING_UPLOAD_TIMEOUT = 30; // seconds
6868
static final String DEFAULT_DEBUGGING_UPLOAD_FORMAT = "binary";
6969
static final boolean DEFAULT_DEBUGGING_CLASSFILE_DUMP_ENABLED = false;
70+
static final int DEFAULT_DEBUGGING_POLL_INTERVAL = 1; // seconds
7071

7172
static final boolean DEFAULT_KAFKA_CLIENT_PROPAGATION_ENABLED = true;
7273

0 commit comments

Comments
 (0)