Skip to content

Commit 68ee2f6

Browse files
authored
Set up virtual field transforms before otel sdk is initialized (#12444)
1 parent dabd5f6 commit 68ee2f6

File tree

18 files changed

+292
-74
lines changed

18 files changed

+292
-74
lines changed

instrumentation/executors/javaagent/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies {
1515
testImplementation(project(":instrumentation:executors:testing"))
1616
testImplementation("org.scala-lang:scala-library:2.11.12")
1717
testCompileOnly(project(":instrumentation:executors:bootstrap"))
18+
testCompileOnly(project(":javaagent-bootstrap"))
1819
}
1920

2021
testing {
@@ -30,6 +31,7 @@ testing {
3031
dependencies {
3132
implementation(project(":instrumentation:executors:testing"))
3233
compileOnly(project(":instrumentation:executors:bootstrap"))
34+
compileOnly(project(":javaagent-bootstrap"))
3335
}
3436

3537
targets {

instrumentation/executors/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/executors/ExecutorsInstrumentationModule.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import com.google.auto.service.AutoService;
1111
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1212
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.javaagent.extension.instrumentation.internal.EarlyInstrumentationModule;
1314
import java.util.List;
1415

15-
@AutoService(InstrumentationModule.class)
16-
public class ExecutorsInstrumentationModule extends InstrumentationModule {
16+
@AutoService({InstrumentationModule.class, EarlyInstrumentationModule.class})
17+
public class ExecutorsInstrumentationModule extends InstrumentationModule
18+
implements EarlyInstrumentationModule {
1719

1820
public ExecutorsInstrumentationModule() {
1921
super("executors");

instrumentation/executors/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/executors/ThreadPoolExecutorTest.java

+7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
import io.opentelemetry.api.baggage.Baggage;
1212
import io.opentelemetry.context.Scope;
13+
import io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker;
1314
import java.util.concurrent.CountDownLatch;
15+
import java.util.concurrent.FutureTask;
1416
import java.util.concurrent.LinkedBlockingQueue;
1517
import java.util.concurrent.ThreadPoolExecutor;
1618
import java.util.concurrent.TimeUnit;
@@ -19,6 +21,11 @@
1921

2022
class ThreadPoolExecutorTest {
2123

24+
@Test
25+
void virtualFieldsAdded() {
26+
assertThat(VirtualFieldInstalledMarker.class).isAssignableFrom(FutureTask.class);
27+
}
28+
2229
@Test
2330
void shouldPassOriginalRunnableToBeforeAfterMethods() throws InterruptedException {
2431
CountDownLatch latch = new CountDownLatch(1);

instrumentation/internal/internal-reflection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionHelper.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import java.lang.reflect.Field;
1212
import java.lang.reflect.Method;
1313
import java.util.ArrayList;
14+
import java.util.Collection;
15+
import java.util.HashSet;
1416
import java.util.List;
1517

1618
public final class ReflectionHelper {
@@ -61,21 +63,23 @@ public static Class<?>[] filterInterfaces(Class<?>[] interfaces, Class<?> contai
6163
return interfaces;
6264
}
6365
List<Class<?>> result = new ArrayList<>(interfaces.length);
66+
Collection<String> virtualFieldClassNames = new HashSet<>();
6467
boolean hasVirtualFieldMarker = false;
6568
for (Class<?> interfaceClass : interfaces) {
6669
// filter out virtual field marker and accessor interfaces
67-
if (interfaceClass == VirtualFieldInstalledMarker.class
68-
|| (VirtualFieldAccessorMarker.class.isAssignableFrom(interfaceClass)
69-
&& interfaceClass.isSynthetic()
70-
&& interfaceClass.getName().contains("VirtualFieldAccessor$"))) {
71-
hasVirtualFieldMarker = true;
70+
if (interfaceClass == VirtualFieldInstalledMarker.class) {
71+
continue;
72+
} else if (VirtualFieldAccessorMarker.class.isAssignableFrom(interfaceClass)
73+
&& interfaceClass.isSynthetic()
74+
&& interfaceClass.getName().contains("VirtualFieldAccessor$")) {
75+
virtualFieldClassNames.add(interfaceClass.getName());
7276
continue;
7377
}
7478
result.add(interfaceClass);
7579
}
7680

77-
if (hasVirtualFieldMarker) {
78-
VirtualFieldDetector.markVirtualFieldsPresent(containingClass);
81+
if (!virtualFieldClassNames.isEmpty()) {
82+
VirtualFieldDetector.markVirtualFields(containingClass, virtualFieldClassNames);
7983
}
8084

8185
return result.toArray(new Class<?>[0]);

javaagent-bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/VirtualFieldDetector.java

+22-12
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,46 @@
66
package io.opentelemetry.javaagent.bootstrap;
77

88
import io.opentelemetry.instrumentation.api.internal.cache.Cache;
9-
import java.util.Arrays;
9+
import java.util.Collection;
1010

1111
/** Helper class for detecting whether given class has virtual fields. */
1212
public final class VirtualFieldDetector {
1313

14-
private static final Cache<Class<?>, Boolean> classesWithVirtualFields = Cache.weak();
14+
// class to virtual field interface dot class names (see
15+
// GeneratedVirtualFieldNames.getFieldAccessorInterfaceName)
16+
private static final Cache<Class<?>, Collection<String>> classesWithVirtualFields = Cache.weak();
1517

1618
private VirtualFieldDetector() {}
1719

1820
/**
19-
* Detect whether given class has virtual fields. This method looks for virtual fields only from
20-
* the specified class not its super classes.
21+
* Detect whether given class has given virtual field. This method looks for virtual fields only
22+
* from the specified class not its super classes.
2123
*
2224
* @param clazz a class
23-
* @return true if given class has virtual fields
25+
* @param virtualFieldInterfaceClassName virtual field interface class dot name
26+
* @return true if given class has the specified virtual field
2427
*/
25-
public static boolean hasVirtualFields(Class<?> clazz) {
28+
public static boolean hasVirtualField(Class<?> clazz, String virtualFieldInterfaceClassName) {
29+
if (!VirtualFieldInstalledMarker.class.isAssignableFrom(clazz)) {
30+
return false;
31+
}
2632
// clazz.getInterfaces() needs to be called before reading from classesWithVirtualFields
2733
// as the call to clazz.getInterfaces() triggers adding clazz to that map via instrumentation
28-
// calling VirtualFieldDetector#markVirtualFieldsPresent() from Class#getInterfaces()
34+
// calling VirtualFieldDetector#markVirtualFields() from Class#getInterfaces()
2935
Class<?>[] interfaces = clazz.getInterfaces();
3036
// to avoid breaking in case internal-reflection instrumentation is disabled check whether
3137
// interfaces array contains virtual field marker interface
32-
if (Arrays.asList(interfaces).contains(VirtualFieldInstalledMarker.class)) {
33-
return true;
38+
for (Class<?> interfaceClass : interfaces) {
39+
if (virtualFieldInterfaceClassName.equals(interfaceClass.getName())) {
40+
return true;
41+
}
3442
}
35-
return classesWithVirtualFields.get(clazz) != null;
43+
44+
Collection<String> virtualFields = classesWithVirtualFields.get(clazz);
45+
return virtualFields != null && virtualFields.contains(virtualFieldInterfaceClassName);
3646
}
3747

38-
public static void markVirtualFieldsPresent(Class<?> clazz) {
39-
classesWithVirtualFields.put(clazz, Boolean.TRUE);
48+
public static void markVirtualFields(Class<?> clazz, Collection<String> virtualFieldClassName) {
49+
classesWithVirtualFields.put(clazz, virtualFieldClassName);
4050
}
4151
}

javaagent-extension-api/src/main/java/io/opentelemetry/javaagent/extension/instrumentation/InstrumentationModule.java

+13-9
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@
3333
*/
3434
public abstract class InstrumentationModule implements Ordered {
3535
private static final Logger logger = Logger.getLogger(InstrumentationModule.class.getName());
36-
private static final boolean indyEnabled;
37-
38-
static {
39-
indyEnabled = ExperimentalConfig.get().indyEnabled();
40-
if (indyEnabled) {
41-
logger.info("Enabled indy for instrumentation modules");
42-
}
43-
}
4436

4537
private final Set<String> instrumentationNames;
4638

@@ -125,7 +117,7 @@ public boolean isHelperClass(String className) {
125117
* techniques. The non-inlining of advice will be enforced by muzzle (TODO)
126118
*/
127119
public boolean isIndyModule() {
128-
return indyEnabled;
120+
return IndyConfigurationHolder.indyEnabled;
129121
}
130122

131123
/** Register resource names to inject into the user's class loader. */
@@ -163,4 +155,16 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
163155
public List<String> getAdditionalHelperClassNames() {
164156
return Collections.emptyList();
165157
}
158+
159+
// InstrumentationModule is loaded before ExperimentalConfig is initialized
160+
private static class IndyConfigurationHolder {
161+
private static final boolean indyEnabled;
162+
163+
static {
164+
indyEnabled = ExperimentalConfig.get().indyEnabled();
165+
if (indyEnabled) {
166+
logger.info("Enabled indy for instrumentation modules");
167+
}
168+
}
169+
}
166170
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.extension.instrumentation.internal;
7+
8+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
9+
import io.opentelemetry.sdk.autoconfigure.spi.Ordered;
10+
11+
/**
12+
* Marker interface for instrumentation modules whose virtual fields should be set up before
13+
* OpenTelemetry SDK is initialized.
14+
*
15+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
16+
* at any time.
17+
*/
18+
public interface EarlyInstrumentationModule extends Ordered {
19+
20+
default InstrumentationModule getInstrumentationModule() {
21+
return (InstrumentationModule) this;
22+
}
23+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/AgentInstaller.java

+65-25
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import static java.util.logging.Level.FINE;
1414
import static java.util.logging.Level.SEVERE;
1515
import static net.bytebuddy.matcher.ElementMatchers.any;
16+
import static net.bytebuddy.matcher.ElementMatchers.none;
1617

1718
import io.opentelemetry.context.Context;
1819
import io.opentelemetry.context.ContextStorage;
@@ -30,11 +31,15 @@
3031
import io.opentelemetry.javaagent.bootstrap.internal.ConfiguredResourceAttributesHolder;
3132
import io.opentelemetry.javaagent.extension.AgentListener;
3233
import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer;
34+
import io.opentelemetry.javaagent.extension.instrumentation.internal.EarlyInstrumentationModule;
3335
import io.opentelemetry.javaagent.tooling.asyncannotationsupport.WeakRefAsyncOperationEndStrategies;
3436
import io.opentelemetry.javaagent.tooling.bootstrap.BootstrapPackagesBuilderImpl;
3537
import io.opentelemetry.javaagent.tooling.bootstrap.BootstrapPackagesConfigurer;
3638
import io.opentelemetry.javaagent.tooling.config.ConfigPropertiesBridge;
3739
import io.opentelemetry.javaagent.tooling.config.EarlyInitAgentConfig;
40+
import io.opentelemetry.javaagent.tooling.field.FieldBackedImplementationConfiguration;
41+
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstaller;
42+
import io.opentelemetry.javaagent.tooling.field.VirtualFieldImplementationInstallerFactory;
3843
import io.opentelemetry.javaagent.tooling.ignore.IgnoredClassLoadersMatcher;
3944
import io.opentelemetry.javaagent.tooling.ignore.IgnoredTypesBuilderImpl;
4045
import io.opentelemetry.javaagent.tooling.ignore.IgnoredTypesMatcher;
@@ -49,6 +54,7 @@
4954
import java.util.HashMap;
5055
import java.util.List;
5156
import java.util.Map;
57+
import java.util.Objects;
5258
import java.util.logging.LogManager;
5359
import java.util.logging.Logger;
5460
import java.util.stream.Stream;
@@ -84,6 +90,8 @@ public class AgentInstaller {
8490

8591
private static final Map<String, List<Runnable>> CLASS_LOAD_CALLBACKS = new HashMap<>();
8692

93+
private static volatile boolean instrumentationInstalled;
94+
8795
public static void installBytebuddyAgent(
8896
Instrumentation inst, ClassLoader extensionClassLoader, EarlyInitAgentConfig earlyConfig) {
8997
addByteBuddyRawSetting();
@@ -98,7 +106,7 @@ public static void installBytebuddyAgent(
98106
if (earlyConfig.getBoolean(JAVAAGENT_ENABLED_CONFIG, true)) {
99107
setupUnsafe(inst);
100108
List<AgentListener> agentListeners = loadOrdered(AgentListener.class, extensionClassLoader);
101-
installBytebuddyAgent(inst, extensionClassLoader, agentListeners);
109+
installBytebuddyAgent(inst, extensionClassLoader, agentListeners, earlyConfig);
102110
} else {
103111
logger.fine("Tracing is disabled, not installing instrumentations.");
104112
}
@@ -107,31 +115,13 @@ public static void installBytebuddyAgent(
107115
private static void installBytebuddyAgent(
108116
Instrumentation inst,
109117
ClassLoader extensionClassLoader,
110-
Iterable<AgentListener> agentListeners) {
118+
Iterable<AgentListener> agentListeners,
119+
EarlyInitAgentConfig earlyConfig) {
111120

112121
WeakRefAsyncOperationEndStrategies.initialize();
113-
114122
EmbeddedInstrumentationProperties.setPropertiesLoader(extensionClassLoader);
115-
116123
setDefineClassHandler();
117-
118-
// If noop OpenTelemetry is enabled, autoConfiguredSdk will be null and AgentListeners are not
119-
// called
120-
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk =
121-
installOpenTelemetrySdk(extensionClassLoader);
122-
123-
ConfigProperties sdkConfig = AgentListener.resolveConfigProperties(autoConfiguredSdk);
124-
AgentInstrumentationConfig.internalInitializeConfig(new ConfigPropertiesBridge(sdkConfig));
125-
copyNecessaryConfigToSystemProperties(sdkConfig);
126-
127-
setBootstrapPackages(sdkConfig, extensionClassLoader);
128-
ConfiguredResourceAttributesHolder.initialize(
129-
SdkAutoconfigureAccess.getResourceAttributes(autoConfiguredSdk));
130-
131-
for (BeforeAgentListener agentListener :
132-
loadOrdered(BeforeAgentListener.class, extensionClassLoader)) {
133-
agentListener.beforeAgent(autoConfiguredSdk);
134-
}
124+
FieldBackedImplementationConfiguration.configure(earlyConfig);
135125

136126
AgentBuilder agentBuilder =
137127
new AgentBuilder.Default(
@@ -153,9 +143,6 @@ private static void installBytebuddyAgent(
153143
if (JavaModule.isSupported()) {
154144
agentBuilder = agentBuilder.with(new ExposeAgentBootstrapListener(inst));
155145
}
156-
157-
agentBuilder = configureIgnoredTypes(sdkConfig, extensionClassLoader, agentBuilder);
158-
159146
if (logger.isLoggable(FINE)) {
160147
agentBuilder =
161148
agentBuilder
@@ -165,6 +152,28 @@ private static void installBytebuddyAgent(
165152
.with(new TransformLoggingListener());
166153
}
167154

155+
installEarlyInstrumentation(agentBuilder, inst);
156+
157+
// If noop OpenTelemetry is enabled, autoConfiguredSdk will be null and AgentListeners are not
158+
// called
159+
AutoConfiguredOpenTelemetrySdk autoConfiguredSdk =
160+
installOpenTelemetrySdk(extensionClassLoader);
161+
162+
ConfigProperties sdkConfig = AgentListener.resolveConfigProperties(autoConfiguredSdk);
163+
AgentInstrumentationConfig.internalInitializeConfig(new ConfigPropertiesBridge(sdkConfig));
164+
copyNecessaryConfigToSystemProperties(sdkConfig);
165+
166+
setBootstrapPackages(sdkConfig, extensionClassLoader);
167+
ConfiguredResourceAttributesHolder.initialize(
168+
SdkAutoconfigureAccess.getResourceAttributes(autoConfiguredSdk));
169+
170+
for (BeforeAgentListener agentListener :
171+
loadOrdered(BeforeAgentListener.class, extensionClassLoader)) {
172+
agentListener.beforeAgent(autoConfiguredSdk);
173+
}
174+
175+
agentBuilder = configureIgnoredTypes(sdkConfig, extensionClassLoader, agentBuilder);
176+
168177
int numberOfLoadedExtensions = 0;
169178
for (AgentExtension agentExtension : loadOrdered(AgentExtension.class, extensionClassLoader)) {
170179
if (logger.isLoggable(FINE)) {
@@ -191,13 +200,44 @@ private static void installBytebuddyAgent(
191200

192201
agentBuilder = AgentBuilderUtil.optimize(agentBuilder);
193202
ResettableClassFileTransformer resettableClassFileTransformer = agentBuilder.installOn(inst);
203+
instrumentationInstalled = true;
194204
ClassFileTransformerHolder.setClassFileTransformer(resettableClassFileTransformer);
195205

196206
addHttpServerResponseCustomizers(extensionClassLoader);
197207

198208
runAfterAgentListeners(agentListeners, autoConfiguredSdk, sdkConfig);
199209
}
200210

211+
private static void installEarlyInstrumentation(
212+
AgentBuilder agentBuilder, Instrumentation instrumentation) {
213+
// We are only going to install the virtual fields here. Installing virtual field changes class
214+
// structure and can not be applied to already loaded classes.
215+
agentBuilder = agentBuilder.with(AgentBuilder.RedefinitionStrategy.DISABLED);
216+
217+
AgentBuilder.Identified.Extendable extendableAgentBuilder =
218+
agentBuilder
219+
.ignore(
220+
target -> instrumentationInstalled, // turn off after instrumentation is installed
221+
Objects::nonNull)
222+
.type(none())
223+
.transform(
224+
(builder, typeDescription, classLoader, module, protectionDomain) -> builder);
225+
226+
VirtualFieldImplementationInstallerFactory virtualFieldInstallerFactory =
227+
new VirtualFieldImplementationInstallerFactory();
228+
for (EarlyInstrumentationModule earlyInstrumentationModule :
229+
loadOrdered(EarlyInstrumentationModule.class, Utils.getExtensionsClassLoader())) {
230+
231+
VirtualFieldImplementationInstaller contextProvider =
232+
virtualFieldInstallerFactory.create(
233+
earlyInstrumentationModule.getInstrumentationModule());
234+
extendableAgentBuilder = contextProvider.injectFields(extendableAgentBuilder);
235+
}
236+
237+
agentBuilder = AgentBuilderUtil.optimize(extendableAgentBuilder);
238+
agentBuilder.installOn(instrumentation);
239+
}
240+
201241
private static void copyNecessaryConfigToSystemProperties(ConfigProperties config) {
202242
for (String property : asList("otel.instrumentation.experimental.span-suppression-strategy")) {
203243
String value = config.getString(property);

0 commit comments

Comments
 (0)