Skip to content

Commit

Permalink
Map OpenTelemetry muzzle references to Datadog equivalent (#7142)
Browse files Browse the repository at this point in the history
* Use extended class-loader to lookup static Muzzle classes
* Generate empty static Muzzle classes for OpenTelemetry extensions
  (they will contribute Muzzle references via the dynamic provider)
* Retain original getMuzzleReferences method
* Map unshaded ASM package to the shaded copy in byte-buddy
* Provide Datadog equivalent of OpenTelemetry ClassRefBuilder
* Map OpenTelemetry ClassRefBuilder to OtelMuzzleRefBuilder and related types
* Lazily build OpenTelemetry muzzle references
* Remove injected helper classes from OpenTelemetry's generated muzzle map
  • Loading branch information
mcculls committed Jun 10, 2024
1 parent 57d6042 commit b7b9e69
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package datadog.opentelemetry.tooling;

import datadog.trace.agent.tooling.ExtensionHandler;
import datadog.trace.agent.tooling.muzzle.Reference;
import datadog.trace.agent.tooling.muzzle.ReferenceMatcher;
import java.net.URL;
import java.net.URLConnection;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;

/** Handles OpenTelemetry instrumentations, so they can be loaded into the Datadog tracer. */
public final class OtelExtensionHandler extends ExtensionHandler {
Expand All @@ -23,17 +29,73 @@ 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 if (file.endsWith("$Muzzle.class")) {
return new JarEntry(file); // pretend we have a static Muzzle class
} else {
return super.mapEntry(jar, file);
}
}

@Override
public URLConnection mapContent(URL url, JarFile jar, JarEntry entry) {
if (entry.getName().endsWith(".class")) {
String file = entry.getName();
if (file.endsWith("$Muzzle.class")) {
return new EmptyMuzzleConnection(url); // generate an empty static Muzzle class
} else if (file.endsWith(".class")) {
return new ClassMappingConnection(url, jar, entry, OtelInstrumentationMapper::new);
} else {
return new JarFileConnection(url, jar, entry);
}
}

/** Generates an empty static muzzle class for OpenTelemetry instrumentations. */
static final class EmptyMuzzleConnection extends ClassMappingConnection {

private static final String REFERENCE_MATCHER_CLASS =
Type.getInternalName(ReferenceMatcher.class);

private static final String REFERENCE_CLASS = Type.getInternalName(Reference.class);

public EmptyMuzzleConnection(URL url) {
super(url, null, null, null);
}

@Override
protected byte[] doMapBytecode(String unused) {
String file = url.getFile();
// remove .class suffix and optional forward-slash prefix
String className = file.substring(file.charAt(0) == '/' ? 1 : 0, file.length() - 6);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(
Opcodes.V1_8,
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
className,
null,
"java/lang/Object",
null);
MethodVisitor mv =
cw.visitMethod(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
"create",
"()L" + REFERENCE_MATCHER_CLASS + ";",
null,
null);
mv.visitCode();
mv.visitTypeInsn(Opcodes.NEW, REFERENCE_MATCHER_CLASS);
mv.visitInsn(Opcodes.DUP);
mv.visitInsn(Opcodes.ICONST_0);
mv.visitTypeInsn(Opcodes.ANEWARRAY, REFERENCE_CLASS);
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
REFERENCE_MATCHER_CLASS,
"<init>",
"([L" + REFERENCE_CLASS + ";)V",
false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ public final class OtelInstrumentationMapper extends ClassRemapper {
Collections.singleton(
"io/opentelemetry/javaagent/tooling/muzzle/InstrumentationModuleMuzzle");

private static final Set<String> UNSUPPORTED_METHODS =
Collections.singleton("getMuzzleReferences");

public OtelInstrumentationMapper(ClassVisitor classVisitor) {
super(classVisitor, Renamer.INSTANCE);
}
Expand All @@ -47,15 +44,6 @@ public void visit(
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 null; // remove unsupported method
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}

private String[] removeUnsupportedTypes(String[] interfaces) {
List<String> filtered = null;
for (int i = interfaces.length - 1; i >= 0; i--) {
Expand All @@ -75,6 +63,8 @@ static final class Renamer extends Remapper {
private static final String OTEL_JAVAAGENT_SHADED_PREFIX =
"io/opentelemetry/javaagent/shaded/io/opentelemetry/";

private static final String ASM_PREFIX = "org/objectweb/asm/";

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

Expand All @@ -97,6 +87,30 @@ static final class Renamer extends Remapper {
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/shaded/instrumentation/api/util/VirtualField",
Type.getInternalName(ContextStore.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/ClassRefBuilder",
Type.getInternalName(OtelMuzzleRefBuilder.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/ClassRef",
Type.getInternalName(OtelMuzzleRefBuilder.ClassRef.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag",
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$VisibilityFlag",
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$MinimumVisibilityFlag",
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$ManifestationFlag",
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$OwnershipFlag",
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/tooling/muzzle/references/Source",
Type.getInternalName(OtelMuzzleRefBuilder.Source.class));
RENAMED_TYPES.put(
"io/opentelemetry/javaagent/bootstrap/Java8BytecodeBridge",
"datadog/trace/bootstrap/otel/Java8BytecodeBridge");
Expand All @@ -113,6 +127,10 @@ public String map(String internalName) {
return "datadog/trace/bootstrap/otel/"
+ internalName.substring(OTEL_JAVAAGENT_SHADED_PREFIX.length());
}
// map unshaded ASM types to the shaded copy in byte-buddy
if (internalName.startsWith(ASM_PREFIX)) {
return "net/bytebuddy/jar/asm/" + internalName.substring(ASM_PREFIX.length());
}
return MAP_LOGGING.apply(internalName);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package datadog.opentelemetry.tooling;

import datadog.opentelemetry.tooling.OtelMuzzleRefBuilder.ClassRef;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.agent.tooling.muzzle.Reference;
import datadog.trace.agent.tooling.muzzle.ReferenceProvider;
import datadog.trace.api.InstrumenterConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.bytebuddy.pool.TypePool;

/**
* Replaces OpenTelemetry's {@code InstrumentationModule} when mapping extensions.
Expand Down Expand Up @@ -36,8 +40,17 @@ private static String[] namespace(String[] names) {
return namespaced;
}

private volatile String[] helperClassNames;

@Override
public String[] helperClassNames() {
if (null == helperClassNames) {
helperClassNames = buildHelperClassNames();
}
return helperClassNames;
}

private String[] buildHelperClassNames() {
List<String> helperClassNames;
List<String> additionalClassNames = getAdditionalHelperClassNames();
if (additionalClassNames.isEmpty()) {
Expand Down Expand Up @@ -85,4 +98,29 @@ public void registerMuzzleVirtualFields(VirtualFieldBuilder builder) {}
public interface VirtualFieldBuilder {
VirtualFieldBuilder register(String typeName, String fieldTypeName);
}

@Override
public ReferenceProvider runtimeMuzzleReferences() {
return new ReferenceProvider() {
private volatile Iterable<Reference> muzzleReferences;

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Iterable<Reference> buildReferences(TypePool ignored) {
if (null == muzzleReferences) {
Map<String, ClassRef> muzzleMap = getMuzzleReferences();
for (String helper : helperClassNames()) {
muzzleMap.remove(helper);
}
muzzleReferences = (Iterable) muzzleMap.values();
}
return muzzleReferences;
}
};
}

/** This method is generated by OpenTelemetry's muzzle plugin at build time. */
public Map<String, ClassRef> getMuzzleReferences() {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package datadog.opentelemetry.tooling;

import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_INTERFACE;
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_FINAL;
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_INTERFACE;
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_PRIVATE;
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_STATIC;
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_PUBLIC;
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_PUBLIC_OR_PROTECTED;
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_STATIC;

import datadog.trace.agent.tooling.muzzle.Reference;
import java.util.Collection;
import net.bytebuddy.jar.asm.Type;

/** Maps OpenTelemetry muzzle references to the Datadog equivalent. */
public final class OtelMuzzleRefBuilder {
private final Reference.Builder builder;

OtelMuzzleRefBuilder(String className) {
this.builder = new Reference.Builder(className);
}

public OtelMuzzleRefBuilder setSuperClassName(String superName) {
builder.withSuperName(superName);
return this;
}

public OtelMuzzleRefBuilder addInterfaceNames(Collection<String> interfaceNames) {
for (String interfaceName : interfaceNames) {
builder.withInterface(interfaceName);
}
return this;
}

public OtelMuzzleRefBuilder addInterfaceName(String interfaceName) {
builder.withInterface(interfaceName);
return this;
}

public OtelMuzzleRefBuilder addSource(String sourceName) {
builder.withSource(sourceName, 0);
return this;
}

public OtelMuzzleRefBuilder addSource(String sourceName, int line) {
builder.withSource(sourceName, line);
return this;
}

public OtelMuzzleRefBuilder addFlag(Flag flag) {
builder.withFlag(flag.bit);
return this;
}

public OtelMuzzleRefBuilder addField(
Source[] sources, Flag[] flags, String fieldName, Type fieldType, boolean isDeclared) {
builder.withField(Source.flatten(sources), Flag.flatten(flags), fieldName, fieldType);
return this;
}

public OtelMuzzleRefBuilder addMethod(
Source[] sources, Flag[] flags, String methodName, Type returnType, Type... argumentTypes) {
builder.withMethod(
Source.flatten(sources), Flag.flatten(flags), methodName, returnType, argumentTypes);
return this;
}

public ClassRef build() {
return new ClassRef(builder.build());
}

public static final class ClassRef extends Reference {
public ClassRef(Reference ref) {
super(
ref.sources,
ref.flags,
ref.className,
ref.superName,
ref.interfaces,
ref.fields,
ref.methods);
}

public static OtelMuzzleRefBuilder builder(String className) {
return new OtelMuzzleRefBuilder(className);
}
}

public enum Flag {
PUBLIC(EXPECTS_PUBLIC),
PROTECTED_OR_HIGHER(EXPECTS_PUBLIC_OR_PROTECTED),
PROTECTED(EXPECTS_PUBLIC_OR_PROTECTED),
PACKAGE_OR_HIGHER(EXPECTS_NON_PRIVATE),
PACKAGE(EXPECTS_NON_PRIVATE),
PRIVATE_OR_HIGHER(0),
PRIVATE(0),
ABSTRACT(0),
FINAL(0),
NON_FINAL(EXPECTS_NON_FINAL),
INTERFACE(EXPECTS_INTERFACE),
NON_INTERFACE(EXPECTS_NON_INTERFACE),
STATIC(EXPECTS_STATIC),
NON_STATIC(EXPECTS_NON_STATIC);

final int bit;

Flag(int bit) {
this.bit = bit;
}

public static int flatten(Flag[] flags) {
int bits = 0;
for (Flag flag : flags) {
bits |= flag.bit;
}
return bits;
}
}

public static final class Source {
final String name;
final int line;

public Source(String name, int line) {
this.name = name;
this.line = line;
}

public static String[] flatten(Source[] sources) {
String[] locations = new String[sources.length];
for (int i = 0; i < sources.length; i++) {
locations[i] = sources[i].name + ":" + sources[i].line;
}
return locations;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private byte[] mapBytecode() {
return BYTECODE_CACHE.computeIfAbsent(url.getFile(), this::doMapBytecode);
}

private byte[] doMapBytecode(String unused) {
protected byte[] doMapBytecode(String unused) {
try (InputStream in = super.getInputStream()) {
ClassReader cr = new ClassReader(in);
ClassWriter cw = new ClassWriter(cr, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.agent.tooling.InstrumenterState;
import datadog.trace.agent.tooling.Utils;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;
import org.slf4j.Logger;
Expand Down Expand Up @@ -55,7 +56,7 @@ private ReferenceMatcher muzzle() {
if (null == muzzle) {
muzzle =
InstrumenterModule.loadStaticMuzzleReferences(
getClass().getClassLoader(), instrumentationClass)
Utils.getExtendedClassLoader(), instrumentationClass)
.withReferenceProvider(runtimeMuzzleReferences);
}
return muzzle;
Expand Down

0 comments on commit b7b9e69

Please sign in to comment.