Skip to content

Commit

Permalink
Debugger DSL (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbachorik committed Jun 23, 2021
1 parent 164f4f6 commit aef8cd9
Show file tree
Hide file tree
Showing 103 changed files with 4,695 additions and 186 deletions.
5 changes: 4 additions & 1 deletion dd-java-agent/agent-debugger/agent-debugger.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ excludedClassesCoverage += [
'com.datadog.debugger.probes.ProbesPoller.1',
'com.datadog.debugger.agent.DebuggerTransformer.SafeClassWriter',
// OS dependent
'com.datadog.debugger.agent.HostId'
'com.datadog.debugger.agent.HostId',
'com.datadog.debugger.*Mixin',
'com.datadog.debugger.agent.DebuggerProbe.When.Threshold'
]

dependencies {
Expand All @@ -31,6 +33,7 @@ dependencies {
compile deps.asmcommons
compile project(':internal-api')
compile project(':dd-java-agent:agent-tooling')
compile project(':dd-java-agent:agent-debugger:debugger-el')
compile project(':dd-java-agent:agent-debugger:debugger-bootstrap')
compile deps.okhttp
compile deps.jackson
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface Sink {

void addDiagnostics(String probeId, List<DiagnosticMessage> messages);

default void skipSnapshot(SnapshotV2 snapshot, long threshold, long duration) {}
default void skipSnapshot(SnapshotV2 snapshot, long duration) {}
}

public interface ProbeResolver {
Expand All @@ -28,12 +28,12 @@ public static void init(Sink sink, ProbeResolver probeResolver) {
DebuggerContext.probeResolver = probeResolver;
}

public static void skipSnapshot(SnapshotV2 snapshot, long threshold, long duration) {
public static void skipSnapshot(SnapshotV2 snapshot, long duration) {
Sink localSink = sink;
if (localSink == null) {
return;
}
localSink.skipSnapshot(snapshot, threshold, duration);
localSink.skipSnapshot(snapshot, duration);
}

public static void addSnapshot(SnapshotV2 snapshot) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package datadog.trace.bootstrap.debugger;

import datadog.trace.bootstrap.debugger.el.DebuggerScript;
import datadog.trace.bootstrap.debugger.el.ReflectiveFieldValueResolver;
import datadog.trace.bootstrap.debugger.el.ValueReferenceResolver;
import datadog.trace.bootstrap.debugger.el.ValueReferences;
import datadog.trace.bootstrap.debugger.el.Values;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -28,6 +34,8 @@ public class SnapshotV2 {
private String traceId; // trace_id
private String spanId; // span_id

private boolean isCapturing = true;

public SnapshotV2(java.lang.Thread thread, ProbeDetails probe) {
this.startTs = System.nanoTime();
this.version = VERSION;
Expand Down Expand Up @@ -65,15 +73,22 @@ public SnapshotV2(
}

public void setEntry(CapturedContext context) {
captures.setEntry(context);
if (checkCapture(context)) {
captures.setEntry(context);
}
}

public void setExit(CapturedContext context) {
captures.setReturn(context);
context.addExtension(ValueReferences.DURATION_EXTENSION_NAME, System.nanoTime() - startTs);
if (checkCapture(context)) {
captures.setReturn(context);
}
}

public void addLine(CapturedContext context, int line) {
captures.addLine(context, line);
if (checkCapture(context)) {
captures.addLine(context, line);
}
}

public void addCaughtException(CapturedContext context) {
Expand Down Expand Up @@ -130,13 +145,12 @@ public String getSpanId() {
return spanId;
}

public void commit(long threshold, boolean lineScope) {
public void commit() {
duration = System.nanoTime() - startTs;
if (threshold > -1 && !lineScope) {
if (duration < threshold) {
DebuggerContext.skipSnapshot(this, threshold, duration);
LOG.warn("Skipping snapshot");
}
if (!isCapturing) {
DebuggerContext.skipSnapshot(this, duration);
LOG.warn("Skipping snapshot");
return;
}
if (!ProbeRateLimiter.tryProbe(probe.id)) {
return;
Expand All @@ -154,6 +168,27 @@ public void commit(long threshold, boolean lineScope) {
DebuggerContext.addSnapshot(this);
}

// /!\ Called by instrumentation /!\
public boolean isCapturing() {
return isCapturing;
}

private boolean checkCapture(CapturedContext capture) {
long startTs = System.nanoTime();
try {
DebuggerScript script = getProbe().getScript();
if (script != null) {
if (!script.execute(capture)) {
isCapturing = false;
}
return isCapturing;
}
} finally {
LOG.debug("Script evaluated in {}ns", (System.nanoTime() - startTs));
}
return true;
}

private void recordStackTrace(int offset) {
stack.clear();
int cntr = 0;
Expand All @@ -170,10 +205,16 @@ public static class ProbeDetails {

private final String id;
private final ProbeLocation location;
private final DebuggerScript script;

public ProbeDetails(String id, ProbeLocation location) {
this(id, location, null);
}

public ProbeDetails(String id, ProbeLocation location, DebuggerScript script) {
this.id = id;
this.location = location;
this.script = script;
}

public String getId() {
Expand All @@ -184,12 +225,18 @@ public ProbeLocation getLocation() {
return location;
}

public DebuggerScript getScript() {
return script;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProbeDetails that = (ProbeDetails) o;
return Objects.equals(id, that.id) && Objects.equals(location, that.location);
return Objects.equals(id, that.id)
&& Objects.equals(location, that.location)
&& Objects.equals(script, that.script);
}

@Override
Expand All @@ -199,7 +246,15 @@ public int hashCode() {

@Override
public String toString() {
return "ProbeDetails{" + "id='" + id + '\'' + ", probeLocation=" + location + '}';
return "ProbeDetails{"
+ "id='"
+ id
+ '\''
+ ", probeLocation="
+ location
+ ", script="
+ script
+ '}';
}
}

Expand Down Expand Up @@ -345,7 +400,9 @@ public String toString() {
}
}

public static class CapturedContext {
public static class CapturedContext implements ValueReferenceResolver {
private final Map<String, Object> extensions = new HashMap<>();

private Map<String, CapturedValue> arguments;
private Map<String, CapturedValue> locals;
private CapturedThrowable throwable;
Expand All @@ -366,11 +423,119 @@ public CapturedContext(
addFields(fields);
}

private CapturedContext(CapturedContext other, Map<String, Object> extensions) {
this.arguments = other.arguments;
this.locals = other.getLocals();
this.throwable = other.throwable;
this.fields = other.fields;
this.extensions.putAll(other.extensions);
this.extensions.putAll(extensions);
}

@Override
public Object resolve(String path) {
// 'path' is a string which starts with a prefixed head element and can contain
// a number of period separated tail elements denoting access to fields (of fields)
String prefix = path.substring(0, 1);
String[] parts = path.substring(1).split("\\.");

String head = parts[0];
Object target = Values.UNDEFINED_OBJECT;
if (prefix.equals(ValueReferences.FIELD_PREFIX)) {
target = tryRetrieveField(head);
} else if (prefix.equals(ValueReferences.SYNTHETIC_PREFIX)) {
target = tryRetrieveSynthetic(head);
} else if (prefix.equals(ValueReferences.LOCALVAR_PREFIX)) {
target = tryRetrieveLocalVar(head);
} else if (prefix.equals(ValueReferences.ARGUMENT_PREFIX)) {
target = tryRetrieveArgument(head);
}
target = followReferences(target, parts);

return target instanceof CapturedValue ? ((CapturedValue) target).getRawValue() : target;
}

private Object tryRetrieveField(String name) {
if (fields == null) {
return Values.UNDEFINED_OBJECT;
}
return fields.containsKey(name) ? fields.get(name) : Values.UNDEFINED_OBJECT;
}

private Object tryRetrieveSynthetic(String name) {
if (extensions == null || extensions.isEmpty()) {
return Values.UNDEFINED_OBJECT;
}
return extensions.containsKey(name) ? extensions.get(name) : Values.UNDEFINED_OBJECT;
}

private Object tryRetrieveLocalVar(String name) {
if (locals == null || locals.isEmpty()) {
return Values.UNDEFINED_OBJECT;
}
return locals.containsKey(name) ? locals.get(name) : Values.UNDEFINED_OBJECT;
}

private Object tryRetrieveArgument(String name) {
if (arguments == null || arguments.isEmpty()) {
return Values.UNDEFINED_OBJECT;
}
return arguments.containsKey(name) ? arguments.get(name) : Values.UNDEFINED_OBJECT;
}

/**
* Will follow the fields as described by the 'parts' argument.
*
* @param src the value to start the field traversing at
* @param parts the reference path
* @return the resolved value or {@linkplain Values#UNDEFINED_OBJECT}
*/
private Object followReferences(Object src, String[] parts) {
if (src == Values.UNDEFINED_OBJECT) {
return src;
}
Object target = src;
// the iteration starts with index 1 as the index 0 was used to resolve the 'src' argument
// in order to avoid extraneous array copies the original array is passed around as is
for (int i = 1; i < parts.length; i++) {
if (target == Values.UNDEFINED_OBJECT) {
break;
}
if (target instanceof CapturedValue) {
Map<String, CapturedValue> fields = ((CapturedValue) target).fields;
if (fields != null && fields.containsKey(parts[i])) {
target = fields.get(parts[i]);
} else {
target = ((CapturedValue) target).getRawValue();
if (target != null) {
target = ReflectiveFieldValueResolver.resolve(target, target.getClass(), parts[i]);
} else {
target = Values.UNDEFINED_OBJECT;
}
}
} else {
target = ReflectiveFieldValueResolver.resolve(target, target.getClass(), parts[i]);
}
}
return target;
}

@Override
public ValueReferenceResolver withExtensions(Map<String, Object> extensions) {
return new CapturedContext(this, extensions);
}

private void addExtension(String name, Object value) {
extensions.put(name, value);
}

public void addArguments(CapturedValue[] values) {
if (values == null) {
return;
}
arguments = new HashMap<>();
if (arguments == null) {
arguments = new HashMap<>();
}
for (CapturedValue value : values) {
arguments.put(value.name, value);
}
Expand All @@ -380,7 +545,9 @@ public void addLocals(CapturedValue[] values) {
if (values == null) {
return;
}
locals = new HashMap<>();
if (locals == null) {
locals = new HashMap<>();
}
for (CapturedValue value : values) {
locals.put(value.name, value);
}
Expand All @@ -393,7 +560,8 @@ public void addReturn(CapturedValue retValue) {
if (locals == null) {
locals = new HashMap<>();
}
locals.put("@return", retValue);
locals.put("@return", retValue); // special local name for the return value
extensions.put(ValueReferences.RETURN_EXTENSION_NAME, retValue);
}

public void addThrowable(Throwable t) {
Expand All @@ -404,7 +572,9 @@ public void addFields(CapturedValue[] values) {
if (values == null) {
return;
}
fields = new HashMap<>();
if (fields == null) {
fields = new HashMap<>();
}
for (CapturedValue value : values) {
fields.put(value.name, value);
}
Expand Down Expand Up @@ -461,12 +631,14 @@ public static class CapturedValue {
private final String name;
private final String type;
private final String value;
private final WeakReference<Object> rawValueRef;
private Map<String, CapturedValue> fields;

private CapturedValue(
String name, String type, Object value, ValueConverter valueConverter, int maxFieldDepth) {
this.name = name;
this.type = type;
this.rawValueRef = new WeakReference<>(value);
this.value = valueConverter.convert(value);
this.fields = maxFieldDepth >= 0 ? FieldExtractor.extract(value, maxFieldDepth) : null;
}
Expand All @@ -479,6 +651,10 @@ public String getValue() {
return value;
}

Object getRawValue() {
return rawValueRef.get();
}

public Map<String, CapturedValue> getFields() {
return fields;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package datadog.trace.bootstrap.debugger.el;

/**
* A debugger EL script interface used for communication between the instrumented code and the
* debugger EL.<br>
* Because it must be reachable from the instrumented code it must be placed in bootstrap.
*/
public interface DebuggerScript {
boolean execute(ValueReferenceResolver valueRefResolver);
}

0 comments on commit aef8cd9

Please sign in to comment.