Versioned helper resources, part 2 (#3880)

* Versioned helper resources, part 2

* Remove accidentally added javadoc param

* Spotless

* Fix test
This commit is contained in:
Trask Stalnaker 2021-08-20 13:52:04 -07:00 committed by GitHub
parent c96af0d51a
commit 5ff7901efc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 39 deletions

View File

@ -58,15 +58,15 @@ public final class InstrumentationModuleInstaller {
return parentAgentBuilder; return parentAgentBuilder;
} }
List<String> helperClassNames = instrumentationModule.getMuzzleHelperClassNames(); List<String> helperClassNames = instrumentationModule.getMuzzleHelperClassNames();
HelperResourceBuilderImpl helperResources = new HelperResourceBuilderImpl(); HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl();
List<String> helperResourceNames = instrumentationModule.helperResourceNames(); List<String> helperResourceNames = instrumentationModule.helperResourceNames();
for (String helperResourceName : helperResourceNames) { for (String helperResourceName : helperResourceNames) {
helperResources.register(helperResourceName); helperResourceBuilder.register(helperResourceName);
} }
instrumentationModule.registerHelperResources(helperResources); instrumentationModule.registerHelperResources(helperResourceBuilder);
List<TypeInstrumentation> typeInstrumentations = instrumentationModule.typeInstrumentations(); List<TypeInstrumentation> typeInstrumentations = instrumentationModule.typeInstrumentations();
if (typeInstrumentations.isEmpty()) { if (typeInstrumentations.isEmpty()) {
if (!helperClassNames.isEmpty() || !helperResources.getResourcePathMappings().isEmpty()) { if (!helperClassNames.isEmpty() || !helperResourceBuilder.getResources().isEmpty()) {
logger.warn( logger.warn(
"Helper classes and resources won't be injected if no types are instrumented: {}", "Helper classes and resources won't be injected if no types are instrumented: {}",
instrumentationModule.instrumentationName()); instrumentationModule.instrumentationName());
@ -82,7 +82,7 @@ public final class InstrumentationModuleInstaller {
new HelperInjector( new HelperInjector(
instrumentationModule.instrumentationName(), instrumentationModule.instrumentationName(),
helperClassNames, helperClassNames,
helperResources.getResourcePathMappings(), helperResourceBuilder.getResources(),
Utils.getExtensionsClassLoader(), Utils.getExtensionsClassLoader(),
instrumentation); instrumentation);
InstrumentationContextProvider contextProvider = InstrumentationContextProvider contextProvider =

View File

@ -28,7 +28,7 @@ class HelperInjectionTest extends Specification {
ClassLoader helpersSourceLoader = new URLClassLoader(helpersSourceUrls) ClassLoader helpersSourceLoader = new URLClassLoader(helpersSourceUrls)
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass' String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
HelperInjector injector = new HelperInjector("test", [helperClassName], [:], helpersSourceLoader, null) HelperInjector injector = new HelperInjector("test", [helperClassName], [], helpersSourceLoader, null)
AtomicReference<URLClassLoader> emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null)) AtomicReference<URLClassLoader> emptyLoader = new AtomicReference<>(new URLClassLoader(new URL[0], (ClassLoader) null))
when: when:
@ -60,7 +60,7 @@ class HelperInjectionTest extends Specification {
ByteBuddyAgent.install() ByteBuddyAgent.install()
AgentInstaller.installBytebuddyAgent(ByteBuddyAgent.getInstrumentation()) AgentInstaller.installBytebuddyAgent(ByteBuddyAgent.getInstrumentation())
String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass' String helperClassName = HelperInjectionTest.getPackage().getName() + '.HelperClass'
HelperInjector injector = new HelperInjector("test", [helperClassName], [:], this.class.classLoader, ByteBuddyAgent.getInstrumentation()) HelperInjector injector = new HelperInjector("test", [helperClassName], [], this.class.classLoader, ByteBuddyAgent.getInstrumentation())
URLClassLoader bootstrapChild = new URLClassLoader(new URL[0], (ClassLoader) null) URLClassLoader bootstrapChild = new URLClassLoader(new URL[0], (ClassLoader) null)
when: when:

View File

@ -9,6 +9,9 @@ dependencies {
// Only used during compilation by bytebuddy plugin // Only used during compilation by bytebuddy plugin
compileOnly("com.google.guava:guava") compileOnly("com.google.guava:guava")
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
api("net.bytebuddy:byte-buddy") api("net.bytebuddy:byte-buddy")
implementation(project(":javaagent-bootstrap")) implementation(project(":javaagent-bootstrap"))

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.tooling;
import io.opentelemetry.instrumentation.api.caching.Cache; import io.opentelemetry.instrumentation.api.caching.Cache;
import io.opentelemetry.javaagent.bootstrap.HelperResources; import io.opentelemetry.javaagent.bootstrap.HelperResources;
import io.opentelemetry.javaagent.tooling.muzzle.HelperResource;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
@ -63,7 +64,7 @@ public class HelperInjector implements Transformer {
private final String requestingName; private final String requestingName;
private final Set<String> helperClassNames; private final Set<String> helperClassNames;
private final Map<String, String> helperResourcePathMappings; private final List<HelperResource> helperResources;
@Nullable private final ClassLoader helpersSource; @Nullable private final ClassLoader helpersSource;
@Nullable private final Instrumentation instrumentation; @Nullable private final Instrumentation instrumentation;
private final Map<String, byte[]> dynamicTypeMap = new LinkedHashMap<>(); private final Map<String, byte[]> dynamicTypeMap = new LinkedHashMap<>();
@ -87,14 +88,14 @@ public class HelperInjector implements Transformer {
public HelperInjector( public HelperInjector(
String requestingName, String requestingName,
List<String> helperClassNames, List<String> helperClassNames,
Map<String, String> helperResourcePathMappings, List<HelperResource> helperResources,
// TODO can this be replaced with the context classloader? // TODO can this be replaced with the context classloader?
ClassLoader helpersSource, ClassLoader helpersSource,
Instrumentation instrumentation) { Instrumentation instrumentation) {
this.requestingName = requestingName; this.requestingName = requestingName;
this.helperClassNames = new LinkedHashSet<>(helperClassNames); this.helperClassNames = new LinkedHashSet<>(helperClassNames);
this.helperResourcePathMappings = helperResourcePathMappings; this.helperResources = helperResources;
this.helpersSource = helpersSource; this.helpersSource = helpersSource;
this.instrumentation = instrumentation; this.instrumentation = instrumentation;
} }
@ -106,7 +107,7 @@ public class HelperInjector implements Transformer {
this.helperClassNames = helperMap.keySet(); this.helperClassNames = helperMap.keySet();
this.dynamicTypeMap.putAll(helperMap); this.dynamicTypeMap.putAll(helperMap);
this.helperResourcePathMappings = Collections.emptyMap(); this.helperResources = Collections.emptyList();
this.helpersSource = null; this.helpersSource = null;
this.instrumentation = instrumentation; this.instrumentation = instrumentation;
} }
@ -149,19 +150,20 @@ public class HelperInjector implements Transformer {
classLoader = injectHelperClasses(typeDescription, classLoader, module); classLoader = injectHelperClasses(typeDescription, classLoader, module);
} }
if (helpersSource != null && !helperResourcePathMappings.isEmpty()) { if (helpersSource != null && !helperResources.isEmpty()) {
for (Map.Entry<String, String> entry : helperResourcePathMappings.entrySet()) { for (HelperResource helperResource : helperResources) {
String agentResourcePath = entry.getValue(); URL resource = helpersSource.getResource(helperResource.getAgentPath());
String applicationResourcePath = entry.getKey();
URL resource = helpersSource.getResource(agentResourcePath);
if (resource == null) { if (resource == null) {
logger.debug("Helper resource {} requested but not found.", agentResourcePath); logger.debug(
"Helper resource {} requested but not found.", helperResource.getAgentPath());
continue; continue;
} }
logger.debug( logger.debug(
"Injecting resource onto classloader {} -> {}", classLoader, applicationResourcePath); "Injecting resource onto classloader {} -> {}",
HelperResources.register(classLoader, applicationResourcePath, resource); classLoader,
helperResource.getApplicationPath());
HelperResources.register(classLoader, helperResource.getApplicationPath(), resource);
} }
} }

View File

@ -82,17 +82,17 @@ public class ClassLoaderMatcher {
try { try {
// verify helper injector works // verify helper injector works
List<String> allHelperClasses = instrumentationModule.getMuzzleHelperClassNames(); List<String> allHelperClasses = instrumentationModule.getMuzzleHelperClassNames();
HelperResourceBuilderImpl helperResources = new HelperResourceBuilderImpl(); HelperResourceBuilderImpl helperResourceBuilder = new HelperResourceBuilderImpl();
List<String> helperResourceNames = instrumentationModule.helperResourceNames(); List<String> helperResourceNames = instrumentationModule.helperResourceNames();
for (String helperResourceName : helperResourceNames) { for (String helperResourceName : helperResourceNames) {
helperResources.register(helperResourceName); helperResourceBuilder.register(helperResourceName);
} }
instrumentationModule.registerHelperResources(helperResources); instrumentationModule.registerHelperResources(helperResourceBuilder);
if (!allHelperClasses.isEmpty()) { if (!allHelperClasses.isEmpty()) {
new HelperInjector( new HelperInjector(
instrumentationModule.instrumentationName(), instrumentationModule.instrumentationName(),
allHelperClasses, allHelperClasses,
helperResources.getResourcePathMappings(), helperResourceBuilder.getResources(),
Thread.currentThread().getContextClassLoader(), Thread.currentThread().getContextClassLoader(),
null) null)
.transform(null, null, classLoader, null); .transform(null, null, classLoader, null);

View File

@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.muzzle;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class HelperResource {
/**
* Create a new helper resource object.
*
* @param applicationPath The path in the user's class loader at which to inject the resource.
* @param agentPath The path in the agent class loader from which to get the content for the
* resource.
*/
public static HelperResource create(String applicationPath, String agentPath) {
return new AutoValue_HelperResource(applicationPath, agentPath);
}
/** The path in the user's class loader at which to inject the resource. */
public abstract String getApplicationPath();
/** The path in the agent class loader from which to get the content for the resource. */
public abstract String getAgentPath();
}

View File

@ -6,30 +6,24 @@
package io.opentelemetry.javaagent.tooling.muzzle; package io.opentelemetry.javaagent.tooling.muzzle;
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder; import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
import java.util.HashMap; import java.util.ArrayList;
import java.util.Map; import java.util.List;
public class HelperResourceBuilderImpl implements HelperResourceBuilder { public class HelperResourceBuilderImpl implements HelperResourceBuilder {
private final Map<String, String> resourcePathMappings = new HashMap<>(); private final List<HelperResource> resources = new ArrayList<>();
@Override @Override
public void register(String resourcePath) { public void register(String resourcePath) {
resourcePathMappings.put(resourcePath, resourcePath); resources.add(HelperResource.create(resourcePath, resourcePath));
} }
@Override @Override
public void register(String applicationResourcePath, String agentResourcePath) { public void register(String applicationResourcePath, String agentResourcePath) {
resourcePathMappings.put(applicationResourcePath, agentResourcePath); resources.add(HelperResource.create(applicationResourcePath, agentResourcePath));
} }
/** public List<HelperResource> getResources() {
* Returns the registered mappings, where the keys are the paths in the user's class loader at return resources;
* which to inject the resource ({@code applicationResourcePath}) and the values are the paths in
* the agent class loader from which to get the content for the resource ({@code
* agentResourcePath}).
*/
public Map<String, String> getResourcePathMappings() {
return resourcePathMappings;
} }
} }

View File

@ -74,14 +74,28 @@ public final class ReferenceCollector {
* *
* @param resource path to the resource file, same as in {@link ClassLoader#getResource(String)} * @param resource path to the resource file, same as in {@link ClassLoader#getResource(String)}
* @see InstrumentationClassPredicate * @see InstrumentationClassPredicate
* @deprecated Use {@link #collectReferencesFromResource(HelperResource)} instead.
*/ */
@Deprecated
public void collectReferencesFromResource(String resource) { public void collectReferencesFromResource(String resource) {
if (!isSpiFile(resource)) { collectReferencesFromResource(HelperResource.create(resource, resource));
}
/**
* If passed {@code resource} path points to an SPI file (either Java {@link
* java.util.ServiceLoader} or AWS SDK {@code ExecutionInterceptor}) reads the file and adds every
* implementation as a reference, traversing the graph of classes until a non-instrumentation
* (external) class is encountered.
*
* @see InstrumentationClassPredicate
*/
public void collectReferencesFromResource(HelperResource helperResource) {
if (!isSpiFile(helperResource.getApplicationPath())) {
return; return;
} }
List<String> spiImplementations = new ArrayList<>(); List<String> spiImplementations = new ArrayList<>();
try (InputStream stream = getResourceStream(resource)) { try (InputStream stream = getResourceStream(helperResource.getAgentPath())) {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, UTF_8)); BufferedReader reader = new BufferedReader(new InputStreamReader(stream, UTF_8));
while (reader.ready()) { while (reader.ready()) {
String line = reader.readLine(); String line = reader.readLine();
@ -90,7 +104,7 @@ public final class ReferenceCollector {
} }
} }
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Error reading resource " + resource, e); throw new IllegalStateException("Error reading resource " + helperResource.getAgentPath(), e);
} }
visitClassesAndCollectReferences(spiImplementations, /* startsFromAdviceClass= */ false); visitClassesAndCollectReferences(spiImplementations, /* startsFromAdviceClass= */ false);