Skip to content

Commit

Permalink
add module opener
Browse files Browse the repository at this point in the history
  • Loading branch information
SylvainJuge committed Dec 13, 2024
1 parent dd05ff3 commit 1a9a687
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
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");
}
Expand All @@ -24,4 +29,11 @@ public RmiContextPropagationInstrumentationModule() {
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new RmiClientContextInstrumentation(), new RmiServerContextInstrumentation());
}

@Override
public Map<String, List<String>> jpmsModulesToOpen() {
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,18 @@ 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" name as key, list of packages as value.
*/
// TODO: copy the javadoc of the original implementation
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 {

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,
() ->
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);
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

0 comments on commit 1a9a687

Please sign in to comment.