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

make rmi instrumentation indy-compatible + add module opener #12585

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,30 @@
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.instrumentation.rmi.context.client.RmiClientContextInstrumentation;
import io.opentelemetry.javaagent.instrumentation.rmi.context.server.RmiServerContextInstrumentation;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@AutoService(InstrumentationModule.class)
public class RmiContextPropagationInstrumentationModule extends InstrumentationModule {
public class RmiContextPropagationInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public RmiContextPropagationInstrumentationModule() {
super("rmi", "rmi-context-propagation");
}

@Override
public boolean isIndyModule() {
// java.lang.IllegalAccessError: class
// io.opentelemetry.javaagent.instrumentation.rmi.context.client.RmiClientContextInstrumentation$StreamRemoteCallConstructorAdvice (in unnamed module @0x740ee00f) cannot access class sun.rmi.transport.Connection (in module java.rmi) because module java.rmi does not export sun.rmi.transport to unnamed module @0x740ee00f
return false;
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation());
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation());
public Map<String, List<String>> jpmsModulesToOpen() {
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved
String witnessClass = "sun.rmi.transport.StreamRemoteCall";
return Collections.singletonMap(
witnessClass, Arrays.asList("sun.rmi.server", "sun.rmi.transport"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
@AutoService(InstrumentationModule.class)
public class RmiJpmsInstrumentationModule extends InstrumentationModule {

// TODO: this instrumentation is only kept for inlined advices and will be removed once migration
// to indy instrumentation is complete.
//
// For Indy instrumentation, this is replaced with
// ExperimentalInstrumentationModule#jpmsModulesToOpen

public RmiJpmsInstrumentationModule() {
super("rmi", "rmi-jpms");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
Expand Down Expand Up @@ -61,4 +62,17 @@ default String getModuleGroup() {
default List<String> agentPackagesToHide() {
return Collections.emptyList();
}

/**
* Some instrumentation need to access JPMS modules that are not accessible by default, this
* method provides a way to access those classes like the "--add-opens" JVM command. <br>
* Map key is the name of a "witness class" belonging to the module that will be loaded and used
* to get a reference to the module. <br>
* Map value is a list of packages to open in the module
*
* @return map of "witness class" FQN as key, list of packages as value.
*/
default Map<String, List<String>> jpmsModulesToOpen() {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.tooling;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;

import java.lang.instrument.Instrumentation;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

/**
* Module opener provides ability to open JPMS modules and allows instrumentation classloader to
* access module contents without requiring JVM arguments modification. <br>
* Usage of this class must be guarded with an {@code net.bytebuddy.utility.JavaModule#isSupported}
* check as it's compiled for Java 9+, otherwise an {@link UnsupportedClassVersionError} will be
* thrown for java 8.
*/
public class ModuleOpener {
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved

private static final Logger logger = Logger.getLogger(ModuleOpener.class.getName());

private ModuleOpener() {}

/**
* Opens JPMS module to a class loader unnamed module
*
* @param classFromTargetModule class from target module
* @param openTo class loader to open module for
* @param packagesToOpen packages to open
*/
public static void open(
Instrumentation instrumentation,
Class<?> classFromTargetModule,
ClassLoader openTo,
Collection<String> packagesToOpen) {

Module targetModule = classFromTargetModule.getModule();
Module openToModule = openTo.getUnnamedModule();
Set<Module> openToModuleSet = Collections.singleton(openToModule);
Map<String, Set<Module>> missingOpens = new HashMap<>();
for (String packageName : packagesToOpen) {
if (!targetModule.isOpen(packageName, openToModule)) {
missingOpens.put(packageName, openToModuleSet);
logger.log(
FINE,
"Exposing package '{0}' in module '{1}' to module '{2}'",
new Object[] {packageName, targetModule, openToModule});
}
}
if (missingOpens.isEmpty()) {
return;
}

if (!instrumentation.isModifiableModule(targetModule)) {
logger.log(WARNING, "Module '{0}' can't be modified", targetModule);
return;
}

try {
instrumentation.redefineModule(
targetModule,
Collections.<Module>emptySet(), // reads
Collections.<String, Set<Module>>emptyMap(), // exports
missingOpens, // opens
Collections.<Class<?>>emptySet(), // uses
Collections.<Class<?>, List<Class<?>>>emptyMap() // provides
);
} catch (Exception e) {
logger.log(WARNING, "unable to redefine module", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@

package io.opentelemetry.javaagent.tooling.instrumentation.indy;

import io.opentelemetry.javaagent.bootstrap.InstrumentationHolder;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import io.opentelemetry.javaagent.tooling.ModuleOpener;
import io.opentelemetry.javaagent.tooling.util.ClassLoaderValue;
import java.lang.instrument.Instrumentation;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.utility.JavaModule;

public class IndyModuleRegistry {

Expand Down Expand Up @@ -65,6 +69,43 @@ public static InstrumentationModuleClassLoader getInstrumentationClassLoader(
+ " yet");
}

if (module instanceof ExperimentalInstrumentationModule) {
ExperimentalInstrumentationModule experimentalModule =
(ExperimentalInstrumentationModule) module;

// Opening JPMS modules requires to use a 'witness class' in the target module to get a
// reference to the module, which means we have to eagerly load the class.
//
// However, this code here triggered when the advice is being executed for the first time so
// this only creates a very small eager loading that is unlikely to have impact on the
// application.
//
// Also, using a class that is already loaded like the one that is being instrumented or a
// related one would increase the likeliness of not having an effect on application class
// loading.

Instrumentation instrumentation = InstrumentationHolder.getInstrumentation();
if (instrumentation == null) {
throw new IllegalStateException("global instrumentation not available");
}

if (JavaModule.isSupported()) {
// module opener only usable for java 9+
experimentalModule
.jpmsModulesToOpen()
.forEach(
(className, packages) -> {
Class<?> type;
try {
type = Class.forName(className, false, instrumentedClassLoader);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("missing witness class " + className, e);
}

ModuleOpener.open(instrumentation, type, loader, packages);
});
}
}
return loader;
}

Expand Down
Loading