Skip to content

Commit

Permalink
Map basic OpenTelemetry instrumentation types to their Datadog equiva…
Browse files Browse the repository at this point in the history
…lents
  • Loading branch information
mcculls committed May 29, 2024
1 parent 86c104f commit c771141
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 1 deletion.
2 changes: 2 additions & 0 deletions dd-java-agent/agent-builder/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ excludedClassesCoverage += ['datadog.trace.agent.tooling.*']
dependencies {
api project(':dd-java-agent:agent-tooling')

implementation project(':dd-java-agent:agent-otel:otel-tooling')

testImplementation project(':dd-java-agent:testing')
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package datadog.trace.agent.tooling;

import static datadog.opentelemetry.tooling.OtelExtensionHandler.OPENTELEMETRY;
import static datadog.trace.agent.tooling.ExtensionHandler.DATADOG;

import de.thetaphi.forbiddenapis.SuppressForbidden;
Expand All @@ -23,7 +24,7 @@
public final class ExtensionFinder {
private static final Logger log = LoggerFactory.getLogger(ExtensionFinder.class);

private static final ExtensionHandler[] handlers = {DATADOG};
private static final ExtensionHandler[] handlers = {OPENTELEMETRY, DATADOG};

/**
* Discovers extensions on the configured path and creates a classloader for each extension.
Expand Down
2 changes: 2 additions & 0 deletions dd-java-agent/agent-otel/otel-tooling/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ minimumBranchCoverage = 0.0
dependencies {
api deps.bytebuddy
api deps.bytebuddyagent

compileOnly project(':dd-java-agent:agent-tooling')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package datadog.opentelemetry.tooling;

import datadog.trace.agent.tooling.ExtensionHandler;
import java.net.URL;
import java.net.URLConnection;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/** Handles OpenTelemetry instrumentations, so they can be loaded into the Datadog tracer. */
public final class OtelExtensionHandler extends ExtensionHandler {

/** Handler for loading externally built OpenTelemetry extensions. */
public static final OtelExtensionHandler OPENTELEMETRY = new OtelExtensionHandler();

private static final String OPENTELEMETRY_MODULE_DESCRIPTOR =
"META-INF/services/io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule";

private static final String DATADOG_MODULE_DESCRIPTOR =
"META-INF/services/datadog.trace.agent.tooling.InstrumenterModule";

@Override
public JarEntry mapEntry(JarFile jar, String file) {
if (DATADOG_MODULE_DESCRIPTOR.equals(file)) {
// redirect request to include OpenTelemetry instrumentations
return super.mapEntry(jar, OPENTELEMETRY_MODULE_DESCRIPTOR);
} else {
return super.mapEntry(jar, file);
}
}

@Override
public URLConnection mapContent(URL url, JarFile jar, JarEntry entry) {
if (entry.getName().endsWith(".class")) {
return new ClassMappingConnection(url, jar, entry, OtelInstrumentationMapper::new);
} else {
return new JarFileConnection(url, jar, entry);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package datadog.opentelemetry.tooling;

import static datadog.trace.agent.tooling.ExtensionHandler.MAP_LOGGING;

import datadog.trace.agent.tooling.InstrumenterModule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.commons.ClassRemapper;
import net.bytebuddy.jar.asm.commons.Remapper;

/** Maps OpenTelemetry instrumentations to use the Datadog {@link InstrumenterModule} API. */
public final class OtelInstrumentationMapper extends ClassRemapper {

private static final Set<String> UNSUPPORTED_TYPES =
new HashSet<>(
Arrays.asList("io/opentelemetry/javaagent/tooling/muzzle/InstrumentationModuleMuzzle"));

private static final Set<String> UNSUPPORTED_METHODS =
new HashSet<>(
Arrays.asList(
"getMuzzleReferences", "getMuzzleHelperClassNames", "registerMuzzleVirtualFields"));

public OtelInstrumentationMapper(ClassVisitor classVisitor) {
super(classVisitor, Renamer.INSTANCE);
}

@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, removeUnsupportedTypes(interfaces));
}

@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
if (!UNSUPPORTED_METHODS.contains(name)) {
return super.visitMethod(access, name, descriptor, signature, exceptions);
} else {
return null; // remove unsupported method
}
}

private String[] removeUnsupportedTypes(String[] interfaces) {
List<String> filtered = null;
for (int i = interfaces.length - 1; i >= 0; i--) {
if (UNSUPPORTED_TYPES.contains(interfaces[i])) {
if (null == filtered) {
filtered = new ArrayList<>(Arrays.asList(interfaces));
}
filtered.remove(i); // remove unsupported interface
}
}
return null != filtered ? filtered.toArray(new String[0]) : interfaces;
}

static final class Renamer extends Remapper {
static final Renamer INSTANCE = new Renamer();

private static final String OTEL_JAVAAGENT_SHADED_PREFIX =
"io/opentelemetry/javaagent/shaded/io/opentelemetry/";

/** Datadog equivalent of OpenTelemetry instrumentation classes. */
private static final Map<String, String> RENAMED_TYPES = new HashMap<>();

static {
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/extension/instrumentation/InstrumentationModule",
"datadog/opentelemetry/tooling/OtelInstrumenterModule");
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/extension/instrumentation/TypeInstrumentation",
"datadog/opentelemetry/tooling/OtelInstrumenter");
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/extension/instrumentation/TypeTransformer",
"datadog/opentelemetry/tooling/OtelTransformer");
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/bootstrap/Java8BytecodeBridge",
"datadog/trace/bootstrap/otel/Java8BytecodeBridge");
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/extension/matcher/AgentElementMatchers",
"datadog/trace/agent/tooling/bytebuddy/matcher/HierarchyMatchers");
}

@Override
public String map(String internalName) {
String rename = RENAMED_TYPES.get(internalName);
if (null != rename) {
return rename;
}
// map OpenTelemetry's shaded API to our embedded copy
if (internalName.startsWith(OTEL_JAVAAGENT_SHADED_PREFIX)) {
return "datadog/trace/bootstrap/otel/"
+ internalName.substring(OTEL_JAVAAGENT_SHADED_PREFIX.length());
}
return MAP_LOGGING.apply(internalName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package datadog.opentelemetry.tooling;

import datadog.trace.agent.tooling.Instrumenter;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

/** Replaces OpenTelemetry's {@code TypeInstrumentation} callback when mapping extensions. */
public interface OtelInstrumenter
extends Instrumenter.ForTypeHierarchy,
Instrumenter.HasMethodAdvice,
Instrumenter.HasTypeAdvice {

@Override
default String hierarchyMarkerType() {
return null; // no hint available
}

@Override
default ElementMatcher<TypeDescription> hierarchyMatcher() {
return typeMatcher();
}

@Override
default void methodAdvice(MethodTransformer methodTransformer) {
OtelTransformerState.capture(this).with(methodTransformer);
}

@Override
default void typeAdvice(TypeTransformer typeTransformer) {
OtelTransformerState.capture(this).with(typeTransformer);
}

ElementMatcher<TypeDescription> typeMatcher();

/**
* Once both transformers have been captured in {@link #methodAdvice} and {@link #typeAdvice} the
* {@code #transform} method will be called. This allows the extension to register method and type
* advice at the same time, using the single interface expected by OpenTelemetry.
*/
void transform(OtelTransformer transformer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package datadog.opentelemetry.tooling;

import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.api.InstrumenterConfig;

/**
* Replaces OpenTelemetry's {@code InstrumentationModule} when mapping extensions.
*
* <p>Original instrumentation names and aliases are prefixed with {@literal "otel."}.
*/
public abstract class OtelInstrumenterModule extends InstrumenterModule.Tracing {

public OtelInstrumenterModule(String instrumentationName, String... additionalNames) {
super(namespace(instrumentationName), namespace(additionalNames));
}

@Override
protected boolean defaultEnabled() {
return InstrumenterConfig.get().isTraceOtelEnabled() && super.defaultEnabled();
}

private static String namespace(String name) {
return "otel." + name;
}

private static String[] namespace(String[] names) {
String[] namespaced = new String[names.length];
for (int i = 0; i < names.length; i++) {
namespaced[i] = namespace(names[i]);
}
return namespaced;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package datadog.opentelemetry.tooling;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;

/** Replaces OpenTelemetry's {@code TypeTransformer} callback when mapping extensions. */
public interface OtelTransformer {

void applyAdviceToMethod(
ElementMatcher<? super MethodDescription> methodMatcher, String adviceClassName);

void applyTransformer(AgentBuilder.Transformer transformer);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package datadog.opentelemetry.tooling;

import datadog.trace.agent.tooling.Instrumenter;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;

/**
* {@link OtelTransformer} state captured when processing OpenTelemetry extensions with {@code
* CombiningTransformerBuilder}. Assumes that the builder is single threaded.
*
* <p>OpenTelemetry has a single transformer callback with methods to register method advice and
* type transformations at the same time, whereas the Datadog tracer has separate {@link
* Instrumenter.MethodTransformer} and {@link Instrumenter.TypeTransformer} callbacks.
*
* <p>To map between the two we capture the Datadog method and type transformers here, from calls to
* {@link OtelInstrumenter}. Once we have captured both transformers we trigger the single transform
* request through the mapped OpenTelemetry callback.
*/
final class OtelTransformerState implements OtelTransformer {
private static final OtelTransformerState CURRENT = new OtelTransformerState();

private OtelInstrumenter instrumenter;
private Instrumenter.MethodTransformer methodTransformer;
private Instrumenter.TypeTransformer typeTransformer;

static OtelTransformerState capture(OtelInstrumenter instrumenter) {
if (instrumenter != CURRENT.instrumenter) {
CURRENT.reset();
CURRENT.instrumenter = instrumenter;
}
return CURRENT;
}

void with(Instrumenter.MethodTransformer methodTransformer) {
this.methodTransformer = methodTransformer;
if (null != this.typeTransformer) {
triggerTransform();
}
}

void with(Instrumenter.TypeTransformer typeTransformer) {
this.typeTransformer = typeTransformer;
if (null != this.methodTransformer) {
triggerTransform();
}
}

private void triggerTransform() {
try {
instrumenter.transform(this);
} finally {
reset();
}
}

private void reset() {
this.instrumenter = null;
this.methodTransformer = null;
this.typeTransformer = null;
}

@Override
public void applyAdviceToMethod(
ElementMatcher<? super MethodDescription> methodMatcher, String adviceClassName) {
methodTransformer.applyAdvice(methodMatcher, adviceClassName);
}

@Override
public void applyTransformer(AgentBuilder.Transformer transformer) {
typeTransformer.applyAdvice(transformer::transform);
}
}

0 comments on commit c771141

Please sign in to comment.