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
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 @@ -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,76 @@
/*
* 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;

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,
() ->
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved
String.format(
"Exposing package '%s' in module '%s' to module '%s'",
packageName, targetModule, openToModule));
}
}
if (missingOpens.isEmpty()) {
return;
}

if (!instrumentation.isModifiableModule(targetModule)) {
logger.log(WARNING, "Module '{}' can't be modified", targetModule);
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved
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,9 +5,12 @@

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;
Expand Down Expand Up @@ -65,6 +68,40 @@ 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");
}

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