Skip to content

Commit 8067519

Browse files
committed
Pull probe definitions from backend
Add a ProbesPoller to poll regularly (1s) from the backend we store classes instrumented to be able to re-tranform classes to remove probes we store probeIds to be able to make a diff of new definitions
1 parent cb40174 commit 8067519

File tree

14 files changed

+553
-48
lines changed

14 files changed

+553
-48
lines changed

dd-java-agent/agent-debugging/agent-debugging.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ excludedClassesCoverage += [
1818
'com.datadog.debugging.parser.*Exception',
1919
// for generics type information trick see com.fasterxml.jackson.core.type.TypeReference javadoc
2020
'com.datadog.debugging.agent.DebuggingAgent.1',
21+
'com.datadog.debugging.agent.ProbesPoller.1',
2122
'com.datadog.debugging.agent.DebuggerTransformer.SafeClassWriter'
2223
]
2324

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

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,19 @@ public static SourceLine fromString(String lineDef) {
7272
public static final class Where {
7373
private String typeName;
7474
private String methodName;
75-
private String source;
75+
private String sourceFile;
7676
private String signature;
7777
private SourceLine[] lines;
7878

79-
Where(String typeName, String methodName, String signature, SourceLine[] lines, String source) {
79+
Where(
80+
String typeName,
81+
String methodName,
82+
String signature,
83+
SourceLine[] lines,
84+
String sourceFile) {
8085
this.typeName = typeName;
8186
this.methodName = methodName;
82-
this.source = source;
87+
this.sourceFile = sourceFile;
8388
this.signature = signature;
8489
this.lines = lines;
8590
}
@@ -90,8 +95,8 @@ public static final class Where {
9095
@JsonProperty("methodName") String methodName,
9196
@JsonProperty("signature") String signature,
9297
@JsonProperty("lines") String[] lines,
93-
@JsonProperty("source") String source) {
94-
this(typeName, methodName, signature, sourceLines(lines), source);
98+
@JsonProperty("sourceFile") String sourceFile) {
99+
this(typeName, methodName, signature, sourceLines(lines), sourceFile);
95100
}
96101

97102
public Where() {}
@@ -116,8 +121,8 @@ public Where lines(String... lines) {
116121
return this;
117122
}
118123

119-
public Where source(String source) {
120-
this.source = source;
124+
public Where sourceFile(String sourceFile) {
125+
this.sourceFile = sourceFile;
121126
return this;
122127
}
123128

@@ -140,8 +145,8 @@ public String getMethodName() {
140145
return methodName;
141146
}
142147

143-
public String getSource() {
144-
return source;
148+
public String getSourceFile() {
149+
return sourceFile;
145150
}
146151

147152
public String getSignature() {
@@ -282,6 +287,8 @@ public static Tag fromString(String def) {
282287
private final int version;
283288
private final String language;
284289
private final String probeId;
290+
private final long orgId;
291+
private final String appId;
285292
private Where where = null;
286293
private When when = null;
287294
private Tag[] tags = null;
@@ -292,12 +299,16 @@ public DebuggerProbe(
292299
@JsonProperty("version") int version,
293300
@JsonProperty("language") String language,
294301
@JsonProperty("probeId") String probeId,
302+
@JsonProperty("orgId") long orgId,
303+
@JsonProperty("appId") String appId,
295304
@JsonProperty("tags") String[] tags,
296305
@JsonProperty("where") Where where,
297306
@JsonProperty("when") When when) {
298307
this.version = version;
299308
this.language = language;
300309
this.probeId = probeId;
310+
this.orgId = orgId;
311+
this.appId = appId;
301312
this.where = where;
302313
this.when = when;
303314
if (tags != null) {
@@ -310,7 +321,7 @@ public DebuggerProbe(
310321
}
311322

312323
public DebuggerProbe() {
313-
this(VERSION, LANGUAGE, UUID.randomUUID().toString(), null, null, null);
324+
this(VERSION, LANGUAGE, UUID.randomUUID().toString(), -1, null, null, null, null);
314325
}
315326

316327
public Where getWhere() {
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.datadog.debugging.agent;
2+
3+
import com.datadog.debugging.probes.ProbesPoller;
4+
import java.lang.instrument.Instrumentation;
5+
import java.lang.instrument.UnmodifiableClassException;
6+
import java.util.ArrayList;
7+
import java.util.HashMap;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Set;
12+
import java.util.function.Function;
13+
import java.util.stream.Collectors;
14+
import lombok.extern.slf4j.Slf4j;
15+
16+
@Slf4j
17+
public class DebuggerProbeRedefinition implements ProbesPoller.ProbeRedefinitionListener {
18+
private final Instrumentation instrumentation;
19+
private final Function<DebuggerProbe[], DebuggerTransformer> transformerSupplier;
20+
private DebuggerTransformer currentTransformer;
21+
private Set<String> probeIds = new HashSet<>();
22+
23+
public DebuggerProbeRedefinition(
24+
Instrumentation instrumentation,
25+
Function<DebuggerProbe[], DebuggerTransformer> transformerSupplier) {
26+
this.instrumentation = instrumentation;
27+
this.transformerSupplier = transformerSupplier;
28+
}
29+
30+
// Should be called by only one thread
31+
public void accept(DebuggerProbe[] debuggerProbes) {
32+
try {
33+
if (debuggerProbes == null || debuggerProbes.length == 0) {
34+
log.debug("No debugger probes defined.");
35+
removeAllProbes();
36+
return;
37+
}
38+
// check already defined probes
39+
Map<String, DebuggerProbe> newDefinitions = new HashMap<>();
40+
for (DebuggerProbe definition : debuggerProbes) {
41+
if (!probeIds.contains(definition.getProbeId())) {
42+
newDefinitions.put(definition.getWhere().getTypeName(), definition);
43+
}
44+
}
45+
if (newDefinitions.isEmpty()) {
46+
return;
47+
}
48+
removeAllProbes();
49+
log.info("Applying new probe definitions by re-transforming classes...");
50+
// install new probe definitions
51+
currentTransformer = transformerSupplier.apply(debuggerProbes);
52+
instrumentation.addTransformer(currentTransformer, true);
53+
log.info("re-transforming {} classes", instrumentation.getAllLoadedClasses().length);
54+
// Build list of classes needed to be re-transformed, matching probe definitions
55+
List<Class<?>> classesToBeTransformed = new ArrayList<>();
56+
for (Class<?> clazz : instrumentation.getAllLoadedClasses()) {
57+
String typeName = clazz.getName().replace('/', '.');
58+
DebuggerProbe debuggerProbe = newDefinitions.get(typeName);
59+
if (debuggerProbe != null) {
60+
classesToBeTransformed.add(clazz);
61+
}
62+
}
63+
// re-transforms matching classes
64+
for (Class<?> clazz : classesToBeTransformed) {
65+
try {
66+
log.info("re-transformed {}", clazz.getCanonicalName());
67+
instrumentation.retransformClasses(clazz);
68+
} catch (Exception ex) {
69+
log.warn("re-transform error: ", ex);
70+
} catch (Throwable ex) {
71+
log.warn("throwable: ", ex);
72+
}
73+
}
74+
probeIds =
75+
newDefinitions.values().stream()
76+
.map(DebuggerProbe::getProbeId)
77+
.collect(Collectors.toSet());
78+
log.info("Re-transformation done.");
79+
} catch (Exception e) {
80+
log.warn("Error during redefinition of the probes: ", e);
81+
}
82+
}
83+
84+
private void removeAllProbes() throws UnmodifiableClassException {
85+
if (currentTransformer == null) {
86+
return;
87+
}
88+
log.info("Remove probes by restoring & re-transforming original class definitions...");
89+
Map<String, Class<?>> originalClasses = currentTransformer.getOriginalClasses();
90+
instrumentation.removeTransformer(currentTransformer);
91+
for (Map.Entry<String, Class<?>> entry : originalClasses.entrySet()) {
92+
Class<?> clazz = entry.getValue();
93+
if (clazz != null) {
94+
log.info("Restoring class {}", clazz.getName());
95+
instrumentation.retransformClasses(clazz);
96+
} else {
97+
log.warn("class null for: {} cannot re-transform", entry.getKey());
98+
}
99+
}
100+
probeIds.clear();
101+
currentTransformer = null;
102+
}
103+
}

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.nio.file.Paths;
1313
import java.nio.file.StandardOpenOption;
1414
import java.security.ProtectionDomain;
15+
import java.util.HashMap;
16+
import java.util.Map;
1517
import lombok.extern.slf4j.Slf4j;
1618
import net.bytebuddy.description.type.TypeDescription;
1719
import net.bytebuddy.dynamic.ClassFileLocator;
@@ -22,23 +24,27 @@
2224
import org.objectweb.asm.tree.MethodNode;
2325

2426
@Slf4j
25-
public final class DebuggerTransformer implements ClassFileTransformer {
27+
public class DebuggerTransformer implements ClassFileTransformer {
2628
private final DebuggerProbe[] probes;
2729
private final Config config;
30+
private final Map<String, Class<?>> originalClasses = new HashMap<>();
2831

2932
public DebuggerTransformer(Config config, DebuggerProbe... probes) {
3033
this.probes = probes;
3134
this.config = config;
3235
}
3336

37+
public Map<String, Class<?>> getOriginalClasses() {
38+
return originalClasses;
39+
}
40+
3441
@Override
3542
public byte[] transform(
3643
ClassLoader loader,
3744
String className,
3845
Class<?> classBeingRedefined,
3946
ProtectionDomain protectionDomain,
4047
byte[] classfileBuffer) {
41-
log.debug("Try transform: {}", className);
4248
if (probes == null || probes.length == 0) {
4349
// no transformation
4450
log.warn("No debugger probes defined.");
@@ -56,6 +62,7 @@ public byte[] transform(
5662
String typeName = className.replace('/', '.');
5763
if (where.isTypeMatching(typeName)) {
5864
log.debug("Matched type '{}'", typeName);
65+
originalClasses.put(className, classBeingRedefined);
5966
ClassReader reader = new ClassReader(classfileBuffer);
6067
dumpOriginalClassFile(className, classfileBuffer);
6168
ClassNode classNode = new ClassNode();
@@ -81,6 +88,8 @@ public byte[] transform(
8188
byte[] data = writer.toByteArray();
8289
dumpInstrumentedClassFile(className, data);
8390
return data;
91+
} else {
92+
log.warn("type matched but no transformation");
8493
}
8594
}
8695
} catch (Exception ex) {
Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.datadog.debugging.agent;
22

3-
import com.fasterxml.jackson.core.type.TypeReference;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
3+
import com.datadog.debugging.probes.ProbesPoller;
54
import datadog.trace.api.Config;
65
import datadog.trace.bootstrap.debugging.DebuggerContext;
76
import java.io.FileInputStream;
@@ -13,25 +12,23 @@
1312
@Slf4j
1413
public class DebuggingAgent {
1514

15+
private static ProbesPoller probesPoller;
16+
1617
public static synchronized void run(Instrumentation instrumentation) {
1718
if (Config.get().isDebuggingEnabled()) {
1819
log.info("starting debugging agent");
1920
SnapshotSink sink = new SnapshotSink(Config.get());
2021
DebuggerContext.init(sink);
21-
String configFileName = System.getProperty("dd.debugger.config-file", "debugger-config.json");
22-
try {
23-
DebuggerProbe[] probes = null;
24-
try (InputStream is = new FileInputStream(configFileName)) {
25-
ObjectMapper mapper = new ObjectMapper();
26-
probes = mapper.readValue(is, new TypeReference<DebuggerProbe[]>() {});
27-
}
28-
instrumentation.addTransformer(new DebuggerTransformer(Config.get(), probes), true);
29-
} catch (IOException ex) {
30-
log.warn(
31-
"Unable to load config file: " + configFileName + ". Debugger agent is disabled.", ex);
32-
}
22+
DebuggerProbeRedefinition debuggerProbeRedefinition =
23+
new DebuggerProbeRedefinition(instrumentation, DebuggingAgent::createTransformer);
24+
probesPoller = new ProbesPoller(Config.get(), debuggerProbeRedefinition);
25+
probesPoller.start();
3326
} else {
3427
log.info("Debugging agent disabled");
3528
}
3629
}
30+
31+
private static DebuggerTransformer createTransformer(DebuggerProbe[] debuggerProbes) {
32+
return new DebuggerTransformer(Config.get(), debuggerProbes);
33+
}
3734
}

0 commit comments

Comments
 (0)