Remove last muzzle generate method from InstrumentationModule (#4281)

* Remove last muzzle generate method from InstrumentationModule
This commit is contained in:
Nikita Salnikov-Tarnovski 2021-10-05 14:43:21 +03:00 committed by GitHub
parent 7473eff1e6
commit 9bbd490288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 52 additions and 111 deletions

View File

@ -20,8 +20,10 @@ Muzzle has two phases:
The compile-time reference collection and code generation process is implemented using a ByteBuddy
plugin (called `MuzzleCodeGenerationPlugin`).
For each instrumentation module the ByteBuddy plugin collects symbols referring to both internal and
third party APIs used by the currently processed module's type
For each instrumentation module the ByteBuddy plugin first applies `InstrumentationModuleMuzzle`
interface to it and then proceeds to implement all methods from that interface by generating the
required bytecode.
It collects symbols referring to both internal and third party APIs used by the currently processed module's type
instrumentations (`InstrumentationModule#typeInstrumentations()`). The reference collection process
starts from advice classes (values of the map returned by the
`TypeInstrumentation#transformers()` method) and traverses the class graph until it encounters a
@ -29,29 +31,23 @@ reference to a non-instrumentation class (determined by `InstrumentationClassPre
the `InstrumentationModule#isHelperClass(String)` predicate). Aside from references,
the collection process also builds a graph of dependencies between internal instrumentation helper
classes - this dependency graph is later used to construct a list of helper classes that will be
injected to the application classloader (`InstrumentationModule#getMuzzleHelperClassNames()`).
Muzzle also automatically generates the `InstrumentationModule#registerMuzzleVirtualFields()`
method.
injected to the application classloader (`InstrumentationModuleMuzzle#getMuzzleHelperClassNames()`).
Muzzle also automatically generates the `InstrumentationModuleMuzzle#registerMuzzleVirtualFields()`
method. All collected references are then used to generate an `InstrumentationModuleMuzzle#getMuzzleReferences` method.
If you extend any of these `getMuzzle...()` methods in your `InstrumentationModule`, the muzzle
compile plugin will not override your code: muzzle will only override those methods that do not have
a custom implementation.
If your `InstrumentationModule` subclass defines a method with exact same signature as a method
from `InstrumentationModuleMuzzle`, the muzzle compile plugin will not override your code:
muzzle will only generate those methods that do not have a custom implementation.
All collected references are then used to create a `ReferenceMatcher` instance. This matcher
is stored in the instrumentation module class in the method `InstrumentationModule#getMuzzleReferenceMatcher()`
and is shared between all type instrumentations. The bytecode of this method (basically an array of
`Reference` builder calls) and the `getMuzzleHelperClassNames()` is generated automatically by the
ByteBuddy plugin using an ASM code visitor.
The source code of the compile-time plugin is located in the `javaagent-tooling` module,
package `io.opentelemetry.javaagent.muzzle.generation.collector`.
The source code of the compile-time plugin is located in the `muzzle` module,
package `io.opentelemetry.javaagent.tooling.muzzle.generation`.
### Runtime reference matching
The runtime reference matching process is implemented as a ByteBuddy matcher in `InstrumentationModule`.
`MuzzleMatcher` uses the `getMuzzleReferenceMatcher()` method generated during the compilation phase
`MuzzleMatcher` uses the `InstrumentationModuleMuzzle` methods generated during the compilation phase
to verify that the class loader of the instrumented type has all necessary symbols (classes,
methods, fields). If the `ReferenceMatcher` finds any mismatch between collected references and the
methods, fields). If this matcher finds any mismatch between collected references and the
actual application classpath types the whole instrumentation is discarded.
It is worth noting that because the muzzle check is expensive, it is only performed after a match
@ -59,8 +55,7 @@ has been made by the `InstrumentationModule#classLoaderMatcher()` and `TypeInstr
matchers. The result of muzzle matcher is cached per classloader, so that it is only executed
once for the whole instrumentation module.
The source code of the runtime muzzle matcher is located in the `javaagent-tooling` module,
in the class `Instrumenter.Default` and under the package `io.opentelemetry.javaagent.tooling.muzzle`.
The source code of the runtime muzzle matcher is located in the `muzzle` module.
## Muzzle gradle plugin

View File

@ -24,7 +24,6 @@ public class LambdaInstrumentationModule extends InstrumentationModule {
return true;
}
@Override
public List<String> getMuzzleHelperClassNames() {
// this instrumentation uses ASM not ByteBuddy so muzzle doesn't automatically add helper
// classes

View File

@ -49,7 +49,6 @@ public class MethodInstrumentationModule extends InstrumentationModule {
// the default configuration has empty "otel.instrumentation.methods.include", and so doesn't
// generate any TypeInstrumentation for muzzle to analyze
@Override
public List<String> getMuzzleHelperClassNames() {
return typeInstrumentations.isEmpty()
? emptyList()

View File

@ -11,7 +11,6 @@ import static net.bytebuddy.matcher.ElementMatchers.any;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.javaagent.extension.Ordered;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@ -135,18 +134,4 @@ public abstract class InstrumentationModule implements Ordered {
/** Returns a list of all individual type instrumentation in this module. */
public abstract List<TypeInstrumentation> typeInstrumentations();
/**
* Returns a list of instrumentation helper classes, automatically detected by muzzle during
* compilation. Those helpers will be injected into the application classloader.
*
* <p>The actual implementation of this method is generated automatically during compilation by
* the {@code io.opentelemetry.instrumentation.javaagent-codegen} Gradle plugin.
*
* <p><b>This method is generated automatically</b>: if you override it, the muzzle compile plugin
* will not generate a new implementation, it will leave the existing one.
*/
public List<String> getMuzzleHelperClassNames() {
return Collections.emptyList();
}
}

View File

@ -59,7 +59,8 @@ public final class InstrumentationModuleInstaller {
logger.debug("Instrumentation {} is disabled", instrumentationModule.instrumentationName());
return parentAgentBuilder;
}
List<String> helperClassNames = instrumentationModule.getMuzzleHelperClassNames();
List<String> helperClassNames =
InstrumentationModuleMuzzle.getMuzzleHelperClassNames(instrumentationModule);
HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl();
instrumentationModule.registerHelperResources(helperResourceBuilder);
List<TypeInstrumentation> typeInstrumentations = instrumentationModule.typeInstrumentations();

View File

@ -77,7 +77,8 @@ public class ClassLoaderMatcher {
List<Mismatch> mismatches) {
try {
// verify helper injector works
List<String> allHelperClasses = instrumentationModule.getMuzzleHelperClassNames();
List<String> allHelperClasses =
InstrumentationModuleMuzzle.getMuzzleHelperClassNames(instrumentationModule);
HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl();
instrumentationModule.registerHelperResources(helperResourceBuilder);
if (!allHelperClasses.isEmpty()) {

View File

@ -7,6 +7,8 @@ package io.opentelemetry.javaagent.tooling.muzzle;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.muzzle.references.ClassRef;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
@ -22,10 +24,34 @@ public interface InstrumentationModuleMuzzle {
*/
Map<String, ClassRef> getMuzzleReferences();
/** See {@link #getMuzzleReferences()}. */
static Map<String, ClassRef> getMuzzleReferences(InstrumentationModule module) {
if (module instanceof InstrumentationModuleMuzzle) {
return ((InstrumentationModuleMuzzle) module).getMuzzleReferences();
} else {
return Collections.emptyMap();
}
}
/**
* Builds the associations between instrumented library classes and instrumentation context
* classes. Keys (and their subclasses) will be associated with a context class stored in the
* value.
*/
void registerMuzzleVirtualFields(VirtualFieldMappingsBuilder builder);
/**
* Returns a list of instrumentation helper classes, automatically detected by muzzle during
* compilation. Those helpers will be injected into the application classloader.
*/
List<String> getMuzzleHelperClassNames();
/** See {@link #getMuzzleHelperClassNames()}. */
static List<String> getMuzzleHelperClassNames(InstrumentationModule module) {
if (module instanceof InstrumentationModuleMuzzle) {
return ((InstrumentationModuleMuzzle) module).getMuzzleHelperClassNames();
} else {
return Collections.emptyList();
}
}
}

View File

@ -185,8 +185,8 @@ public abstract class Mismatch {
}
/**
* Represents failure to inject {@link InstrumentationModule#getMuzzleHelperClassNames()} into
* some classloader.
* Represents failure to inject {@link InstrumentationModuleMuzzle#getMuzzleHelperClassNames()}
* into some classloader.
*/
public static class HelperClassesInjectionError extends Mismatch {

View File

@ -1,66 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.muzzle;
import static java.util.Collections.emptyMap;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.muzzle.references.ClassRef;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is used to access {@code getMuzzleReferences} method from the {@link
* InstrumentationModule} class. That method is not visible in the source code and instead is
* generated automatically during compilation by the {@code
* io.opentelemetry.instrumentation.javaagent-codegen} Gradle plugin.
*/
class MuzzleReferencesAccessor {
private static final Logger logger = LoggerFactory.getLogger(MuzzleReferencesAccessor.class);
private static final ClassValue<MethodHandle> getMuzzleReferences =
new ClassValue<MethodHandle>() {
@Override
protected MethodHandle computeValue(Class<?> type) {
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
MethodHandle handle;
try {
// This method is generated automatically during compilation by
// the io.opentelemetry.instrumentation.javaagent-codegen Gradle plugin.
handle =
lookup.findVirtual(type, "getMuzzleReferences", MethodType.methodType(Map.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
handle = null;
}
return handle;
}
};
/**
* Returns references to helper and library classes used in the given module's type
* instrumentation advices, grouped by {@link ClassRef#getClassName()}.
*/
@SuppressWarnings("unchecked")
static Map<String, ClassRef> getFor(InstrumentationModule instrumentationModule) {
if (instrumentationModule instanceof InstrumentationModuleMuzzle) {
return ((InstrumentationModuleMuzzle) instrumentationModule).getMuzzleReferences();
}
// Older classes created and compiled outside of this repo may not yet have the interface above.
MethodHandle methodHandle = getMuzzleReferences.get(instrumentationModule.getClass());
if (methodHandle != null) {
logger.warn(
"{} is compiled with old version of Muzzle and must be recompiled against newer version of OpenTelemetry Java Instrumentation APIs",
instrumentationModule);
}
return emptyMap();
}
}

View File

@ -39,8 +39,8 @@ public final class ReferenceMatcher {
public static ReferenceMatcher of(InstrumentationModule instrumentationModule) {
return new ReferenceMatcher(
instrumentationModule.getMuzzleHelperClassNames(),
MuzzleReferencesAccessor.getFor(instrumentationModule),
InstrumentationModuleMuzzle.getMuzzleHelperClassNames(instrumentationModule),
InstrumentationModuleMuzzle.getMuzzleReferences(instrumentationModule),
instrumentationModule::isHelperClass);
}

View File

@ -22,14 +22,15 @@ public final class ReferencesPrinter {
/**
* For all {@link InstrumentationModule}s found in the current thread's context classloader this
* method prints references returned by the {@link
* MuzzleReferencesAccessor#getFor(InstrumentationModule)} method to the standard output.
* InstrumentationModuleMuzzle#getMuzzleReferences()} method to the standard output.
*/
public static void printMuzzleReferences() {
for (InstrumentationModule instrumentationModule :
ServiceLoader.load(InstrumentationModule.class)) {
try {
System.out.println(instrumentationModule.getClass().getName());
for (ClassRef ref : MuzzleReferencesAccessor.getFor(instrumentationModule).values()) {
for (ClassRef ref :
InstrumentationModuleMuzzle.getMuzzleReferences(instrumentationModule).values()) {
System.out.print(prettyPrint(ref));
}
} catch (RuntimeException e) {