Skip to content
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

Implement code origin support for grpc server entry spans #7942

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dd-java-agent/agent-bootstrap/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencies {
api project(':dd-trace-api')
api project(':internal-api')
api project(':internal-api:internal-api-9')
api project(':dd-java-agent:agent-logging')
api project(':dd-java-agent:agent-debugger:debugger-bootstrap')
api libs.slf4j
// ^ Generally a bad idea for libraries, but we're shadowing.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -73,6 +74,11 @@ public interface ExceptionDebugger {

public interface CodeOriginRecorder {
String captureCodeOrigin(String signature);

String captureCodeOrigin(AgentSpan span, boolean entry);

String captureCodeOrigin(
String name, Class<?> target, String method, Class<?>[] types, boolean entry);
}

private static volatile ProbeResolver probeResolver;
Expand Down Expand Up @@ -363,6 +369,43 @@ public static String captureCodeOrigin(String signature) {
return null;
}

public static String captureCodeOrigin(Function<CodeOriginRecorder, String> function) {
try {
CodeOriginRecorder recorder = codeOriginRecorder;
if (recorder != null) {
return function.apply(recorder);
}
} catch (Exception ex) {
LOGGER.debug("Error in captureCodeOrigin: ", ex);
}
return null;
}

public static String captureCodeOrigin(AgentSpan span, boolean entry) {
try {
CodeOriginRecorder recorder = codeOriginRecorder;
if (recorder != null) {
return recorder.captureCodeOrigin(span, entry);
}
} catch (Exception ex) {
LOGGER.debug("Error in captureCodeOrigin: ", ex);
}
return null;
}

public static String captureCodeOrigin(
String name, Class<?> target, String method, Class<?>[] types, boolean entry) {
try {
CodeOriginRecorder recorder = codeOriginRecorder;
if (recorder != null) {
return recorder.captureCodeOrigin(name, target, method, types, entry);
}
} catch (Exception ex) {
LOGGER.debug("Error in captureCodeOrigin: ", ex);
}
return null;
}

public static void handleException(Throwable t, AgentSpan span) {
try {
ExceptionDebugger exDebugger = exceptionDebugger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,33 @@ public static void entry(Method method) {
}
}

public static void entry(Class<?> type, Method method) {
if (InstrumenterConfig.get().isCodeOriginEnabled()) {
String signature =
stream(method.getParameterTypes())
.map(Class::getTypeName)
.collect(Collectors.joining(", ", "(", ")"));
captureCodeOrigin(signature);
}
}

public static void entry(AgentSpan span) {
if (InstrumenterConfig.get().isCodeOriginEnabled()) {
captureCodeOrigin(span, true);
}
}

public static void entry(String name, Class<?> target, String method, Class<?>[] types) {
if (InstrumenterConfig.get().isCodeOriginEnabled()) {
captureCodeOrigin(name, target, method, types, true);
}
}

public static void exit(AgentSpan span) {
if (InstrumenterConfig.get().isCodeOriginEnabled()) {
String probeId = captureCodeOrigin(null);
String probeId = captureCodeOrigin((String) null);
if (span != null) {
span.getLocalRootSpan().setTag(probeId, span);
span.getLocalRootSpan().setTag(probeId, span.getSpanId());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.datadog.debugger.codeorigin;

import static com.datadog.debugger.agent.ConfigurationAcceptor.Source.CODE_ORIGIN;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;

import com.datadog.debugger.agent.ConfigurationUpdater;
import com.datadog.debugger.exception.Fingerprinter;
import com.datadog.debugger.probe.CodeOriginProbe;
import com.datadog.debugger.probe.Where;
import com.datadog.debugger.util.ClassFileLines;
import com.datadog.debugger.util.ClassNameFiltering;
import datadog.trace.api.Config;
import datadog.trace.bootstrap.debugger.CapturedContext;
import datadog.trace.bootstrap.debugger.DebuggerContext;
Expand All @@ -15,31 +19,80 @@
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import datadog.trace.util.AgentTaskScheduler;
import datadog.trace.util.stacktrace.StackWalkerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultCodeOriginRecorder implements CodeOriginRecorder {
private static final Logger LOG = LoggerFactory.getLogger(DefaultCodeOriginRecorder.class);

private final Config config;

private final ConfigurationUpdater configurationUpdater;

private final Map<String, CodeOriginProbe> fingerprints = new HashMap<>();

private final Map<String, CodeOriginProbe> probes = new ConcurrentHashMap<>();

private final AgentTaskScheduler taskScheduler = AgentTaskScheduler.INSTANCE;
private final AgentTaskScheduler taskScheduler;

private final int maxUserFrames;

// this really should only be used for testing
public DefaultCodeOriginRecorder() {
maxUserFrames = 8;
configurationUpdater = null;
DebuggerContext.initClassNameFilter(
new ClassNameFiltering(
new HashSet<>(
asList(
"sun",
"org.junit",
"java.",
"org.gradle",
"com.sun",
"worker.org.gradle",
"datadog",
"com.datadog.debugger.probe",
"com.datadog.debugger.codeorigin"))));
new ClassNameFiltering(
new HashSet<>(
asList(
"sun",
"org.junit",
"java.",
"org.gradle",
"com.sun",
"worker.org.gradle",
"datadog",
"com.datadog.debugger.probe",
"com.datadog.debugger.codeorigin")));
taskScheduler = AgentTaskScheduler.INSTANCE;
}

public DefaultCodeOriginRecorder(Config config, ConfigurationUpdater configurationUpdater) {
this.config = config;
this.configurationUpdater = configurationUpdater;
maxUserFrames = config.getDebuggerCodeOriginMaxUserFrames();
taskScheduler = AgentTaskScheduler.INSTANCE;
}

public DefaultCodeOriginRecorder(
Config config, ConfigurationUpdater configurationUpdater, AgentTaskScheduler taskScheduler) {
this.configurationUpdater = configurationUpdater;
maxUserFrames = config.getDebuggerCodeOriginMaxUserFrames();
this.taskScheduler = taskScheduler;
}

@Override
Expand All @@ -66,7 +119,7 @@ public String captureCodeOrigin(String signature) {
new ProbeId(UUID.randomUUID().toString(), 0),
where.getSignature(),
where,
config.getDebuggerCodeOriginMaxUserFrames());
maxUserFrames);
addFingerprint(fingerprint, probe);

installProbe(probe);
Expand All @@ -84,6 +137,84 @@ public String captureCodeOrigin(String signature) {
return probe.getId();
}

@Override
public String captureCodeOrigin(
String name, Class<?> target, String method, Class<?>[] types, boolean entry) {
String fingerprint = Fingerprinter.fingerprint(name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fundamentally, I have reserved to use fingerprinting for only one string. does not make sense to me. add directly the string as the fingerprint

if (fingerprint == null) {
LOG.debug("Unable to fingerprint stack trace");
return null;
}
CodeOriginProbe probe;

if (isAlreadyInstrumented(fingerprint)) {
probe = fingerprints.get(fingerprint);
} else {
Where where =
Where.of(
target.getName(),
method,
stream(types).map(Class::getTypeName).collect(Collectors.joining(", ", "(", ")")));

probe =
new CodeOriginProbe(
new ProbeId(UUID.randomUUID().toString(), 0),
where.getSignature(),
where,
maxUserFrames);
addFingerprint(fingerprint, probe);

installProbe(probe);
// committing here manually so that first run probe encounters decorate the span until the
// instrumentation gets installed
probe.commit(
CapturedContext.EMPTY_CONTEXT, CapturedContext.EMPTY_CONTEXT, Collections.emptyList());
}

return probe.getId();
}

@Override
public String captureCodeOrigin(AgentSpan span, boolean entry) {
String fingerprint = Fingerprinter.fingerprint(span.getResourceName());
if (fingerprint == null) {
LOG.debug("Unable to fingerprint stack trace");
return null;
}
CodeOriginProbe probe;

if (isAlreadyInstrumented(fingerprint)) {
probe = fingerprints.get(fingerprint);
} else {
StackTraceElement element = findPlaceInStack();
ClassNode classNode = parseClassFile(element.getClassName());
ClassFileLines lines = new ClassFileLines(classNode);
List<MethodNode> methodsByLine = lines.getMethodsByLine(element.getLineNumber());
Where where =
Where.of(
element.getClassName(),
element.getMethodName(),
"FIX ME",
String.valueOf(element.getLineNumber()));

probe =
new CodeOriginProbe(
new ProbeId(UUID.randomUUID().toString(), 0),
where.getSignature(),
where,
maxUserFrames);
addFingerprint(fingerprint, probe);

installProbe(probe);
// committing here manually so that first run probe encounters decorate the span until the
// instrumentation gets installed
probe.commit(
CapturedContext.EMPTY_CONTEXT, CapturedContext.EMPTY_CONTEXT, Collections.emptyList());
}

return probe.getId();
}

private StackTraceElement findPlaceInStack() {
return StackWalkerFactory.INSTANCE.walk(
stream ->
Expand All @@ -104,7 +235,9 @@ void addFingerprint(String fingerprint, CodeOriginProbe probe) {
public String installProbe(CodeOriginProbe probe) {
CodeOriginProbe installed = probes.putIfAbsent(probe.getId(), probe);
if (installed == null) {
taskScheduler.execute(() -> configurationUpdater.accept(CODE_ORIGIN, getProbes()));
if (configurationUpdater != null) {
taskScheduler.execute(() -> configurationUpdater.accept(CODE_ORIGIN, getProbes()));
}
return probe.getId();
}
return installed.getId();
Expand All @@ -117,4 +250,24 @@ public CodeOriginProbe getProbe(String probeId) {
public Collection<CodeOriginProbe> getProbes() {
return probes.values();
}

private ClassNode parseClassFile(String className) {
byte[] bytes = new byte[8192];
try (InputStream inputStream =
getClass()
.getClassLoader()
.getResourceAsStream(String.format("%s.class", className.replace('.', '/')))) {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
int bytesRead;
while ((bytesRead = inputStream.read(bytes)) != -1) {
bao.write(bytes, 0, bytesRead);
}
ClassNode classNode = new ClassNode();
new ClassReader(bao.toByteArray()).accept(classNode, ClassReader.SKIP_FRAMES);
return classNode;
} catch (IOException e) {
LOG.error("Can't read class file information for {}", className);
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ public static String fingerprint(Throwable t, ClassNameFilter classNameFiltering
return bytesToHex(digest.digest());
}

public static String fingerprint(CharSequence resourceName) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(resourceName.toString().getBytes());
return bytesToHex(digest.digest());
} catch (NoSuchAlgorithmException e) {
LOGGER.debug("Unable to find digest algorithm SHA-256", e);
return null;
}
}

public static String fingerprint(StackTraceElement element) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.datadog.debugger.agent.DebuggerAgent;
import com.datadog.debugger.instrumentation.InstrumentationResult;
import com.datadog.debugger.sink.DebuggerSink;
import com.datadog.debugger.sink.Snapshot;
import datadog.trace.bootstrap.debugger.CapturedContext;
import datadog.trace.bootstrap.debugger.DebuggerContext;
Expand Down Expand Up @@ -68,16 +69,19 @@ public void commit(
return;
}
String snapshotId = null;
if (isDebuggerEnabled(span)) {
DebuggerSink sink = DebuggerAgent.getSink();
if (isDebuggerEnabled(span) && sink != null) {
Snapshot snapshot = createSnapshot();
if (fillSnapshot(entryContext, exitContext, caughtExceptions, snapshot)) {
snapshotId = snapshot.getId();
LOGGER.debug("committing code origin probe id={}, snapshot id={}", id, snapshotId);
commitSnapshot(snapshot, DebuggerAgent.getSink());
commitSnapshot(snapshot, sink);
}
}
applySpanOriginTags(span, snapshotId);
DebuggerAgent.getSink().getProbeStatusSink().addEmitting(probeId);
if (sink != null) {
sink.getProbeStatusSink().addEmitting(probeId);
}
span.getLocalRootSpan().setTag(getId(), (String) null); // clear possible span reference
}

Expand Down Expand Up @@ -111,6 +115,9 @@ public boolean entrySpanProbe() {

/** look "back" to find exit spans that may have already come and gone */
private AgentSpan findSpan(AgentSpan candidate) {
if (candidate == null) {
return null;
}
AgentSpan span = candidate;
AgentSpan localRootSpan = candidate.getLocalRootSpan();
if (localRootSpan.getTag(getId()) != null) {
Expand Down
Loading