Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions apm-agent-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

<artifactId>apm-agent-core</artifactId>
<name>${project.groupId}:${project.artifactId}</name>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>

<properties>
<maven-deploy-plugin.skip>true</maven-deploy-plugin.skip>
Expand All @@ -18,6 +24,11 @@
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>apm-agent-plugin-sdk</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
Expand All @@ -29,11 +40,6 @@
<version>2.1.2</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.slf4j}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
Expand All @@ -49,22 +55,12 @@
<artifactId>log4j2-ecs-layout</artifactId>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>com.blogspot.mydailyjava</groupId>
<artifactId>weak-lock-free</artifactId>
<version>0.15</version>
</dependency>

<dependency>
<groupId>org.stagemonitor</groupId>
<artifactId>stagemonitor-configuration</artifactId>
<version>0.87.3</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-dep</artifactId>
<version>${version.byte-buddy}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Expand All @@ -24,3 +24,5 @@
*/
@NonnullApi
package co.elastic.apm.agent.annotation;

import co.elastic.apm.agent.sdk.NonnullApi;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 - 2020 Elastic and contributors
* %%
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* #L%
*/
package co.elastic.apm.agent.bci;

import co.elastic.apm.agent.sdk.DynamicTransformer;
import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;

import java.util.Collection;

public class DynamicTransformerImpl implements DynamicTransformer {

@Override
public void ensureInstrumented(Class<?> classToInstrument, Collection<Class<? extends ElasticApmInstrumentation>> instrumentationClasses) {
ElasticApmAgent.ensureInstrumented(classToInstrument, instrumentationClasses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,18 @@
import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.agent.bci.bytebuddy.SoftlyReferencingTypePoolCache;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToPostProcessorFactory;
import co.elastic.apm.agent.bci.classloading.ExternalPluginClassLoader;
import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentation;
import co.elastic.apm.agent.collections.WeakMapSupplier;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import co.elastic.apm.agent.impl.GlobalTracer;
import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
import co.elastic.apm.agent.sdk.weakmap.WeakMapSupplier;
import co.elastic.apm.agent.util.DependencyInjectingServiceLoader;
import co.elastic.apm.agent.util.ExecutorUtils;
import co.elastic.apm.agent.util.ObjectUtils;
import co.elastic.apm.agent.util.ThreadUtils;
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
import net.bytebuddy.ByteBuddy;
Expand All @@ -69,6 +72,7 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor;
Expand All @@ -78,6 +82,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
Expand Down Expand Up @@ -115,6 +120,7 @@ public class ElasticApmAgent {
private static final WeakConcurrentMap<Class<?>, Set<Collection<Class<? extends ElasticApmInstrumentation>>>> dynamicallyInstrumentedClasses = WeakMapSupplier.createMap();
@Nullable
private static File agentJarFile;
private static final Map<String, ClassLoader> pluginClassLoaderByAdviceClass = new ConcurrentHashMap<>();

/**
* Called reflectively by {@link AgentMain} to initialize the agent
Expand Down Expand Up @@ -144,14 +150,51 @@ private static void initInstrumentation(ElasticApmTracer tracer, Instrumentation

@Nonnull
private static Iterable<ElasticApmInstrumentation> loadInstrumentations(ElasticApmTracer tracer) {
final List<ElasticApmInstrumentation> instrumentations = DependencyInjectingServiceLoader.load(ElasticApmInstrumentation.class, tracer);
List<ClassLoader> pluginClassLoaders = new ArrayList<>();
pluginClassLoaders.add(ElasticApmAgent.class.getClassLoader());
pluginClassLoaders.addAll(createExternalPluginClassLoaders(tracer.getConfig(CoreConfiguration.class).getPluginsDir()));
final List<ElasticApmInstrumentation> instrumentations = DependencyInjectingServiceLoader.load(ElasticApmInstrumentation.class, pluginClassLoaders, tracer);
for (MethodMatcher traceMethod : tracer.getConfig(CoreConfiguration.class).getTraceMethods()) {
instrumentations.add(new TraceMethodInstrumentation(tracer, traceMethod));
}

return instrumentations;
}

private static Collection<? extends ClassLoader> createExternalPluginClassLoaders(@Nullable String pluginsDirString) {
final Logger logger = LoggerFactory.getLogger(ElasticApmAgent.class);
if (pluginsDirString == null) {
logger.debug("No plugins dir");
return Collections.emptyList();
}
File pluginsDir = new File(pluginsDirString);
if (!pluginsDir.exists()) {
logger.debug("Plugins dir does not exist: {}", pluginsDirString);
return Collections.emptyList();
}
File[] pluginJars = pluginsDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
if (pluginJars == null) {
logger.info("Invalid plugins dir {}", pluginsDirString);
return Collections.emptyList();
}
List<ClassLoader> result = new ArrayList<>(pluginJars.length);
for (File pluginJar : pluginJars) {
try {
result.add(new ExternalPluginClassLoader(pluginJar, ElasticApmAgent.class.getClassLoader()));
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
logger.info("Loading plugin {}", pluginJar.getName());
}
return result;
}



public static synchronized void initInstrumentation(final ElasticApmTracer tracer, Instrumentation instrumentation,
Iterable<ElasticApmInstrumentation> instrumentations) {
initInstrumentation(tracer, instrumentation, instrumentations, false);
Expand All @@ -163,6 +206,11 @@ private static synchronized void initInstrumentation(final ElasticApmTracer trac
return;
}
GlobalTracer.set(tracer);
for (ElasticApmInstrumentation apmInstrumentation : instrumentations) {
pluginClassLoaderByAdviceClass.put(
apmInstrumentation.getAdviceClass().getName(),
ObjectUtils.systemClassLoaderIfNull(apmInstrumentation.getClass().getClassLoader()));
}
Runtime.getRuntime().addShutdownHook(new Thread(ThreadUtils.addElasticApmThreadPrefix("init-instrumentation-shutdown-hook")) {
@Override
public void run() {
Expand Down Expand Up @@ -266,7 +314,7 @@ private static AgentBuilder applyAdvice(final ElasticApmTracer tracer, final Age
final boolean typeMatchingWithNamePreFilter = tracer.getConfig(CoreConfiguration.class).isTypeMatchingWithNamePreFilter();
final ElementMatcher.Junction<ClassLoader> classLoaderMatcher = instrumentation.getClassLoaderMatcher();
final ElementMatcher<? super NamedElement> typeMatcherPreFilter = instrumentation.getTypeMatcherPreFilter();
final ElementMatcher.Junction<ProtectionDomain> versionPostFilter = instrumentation.getImplementationVersionPostFilter();
final ElementMatcher.Junction<ProtectionDomain> versionPostFilter = instrumentation.getProtectionDomainPostFilter();
final ElementMatcher<? super MethodDescription> methodMatcher = new ElementMatcher.Junction.Conjunction<>(instrumentation.getMethodMatcher(), not(isAbstract()));
return agentBuilder
.type(new AgentBuilder.RawMatcher() {
Expand Down Expand Up @@ -339,8 +387,10 @@ private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticAp
if (offsetMapping != null) {
withCustomMapping = withCustomMapping.bind(offsetMapping);
}
if (instrumentation.indyPlugin()) {
validateAdvice(instrumentation.getAdviceClass().getName());
// external plugins are always indy plugins
if (!(instrumentation instanceof TracerAwareInstrumentation)
|| ((TracerAwareInstrumentation) instrumentation).indyPlugin()) {
validateAdvice(instrumentation.getAdviceClass());
withCustomMapping = withCustomMapping.bootstrap(IndyBootstrap.getIndyBootstrapMethod());
}
return new AgentBuilder.Transformer.ForAdvice(withCustomMapping)
Expand All @@ -366,17 +416,22 @@ public boolean matches(MethodDescription target) {
}
}
}, instrumentation.getAdviceClass().getName())
.include(ClassLoader.getSystemClassLoader())
.include(ClassLoader.getSystemClassLoader(), instrumentation.getClass().getClassLoader())
.withExceptionHandler(PRINTING);
}

/**
* Validates invariants explained in {@link ElasticApmInstrumentation#indyPlugin()}
* Validates invariants explained in {@link TracerAwareInstrumentation#indyPlugin()}
*
* @param adviceClassName the name of the advice class
* @param adviceClass the advice class
*/
private static void validateAdvice(String adviceClassName) {
TypePool pool = new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.NoOp.INSTANCE, ClassFileLocator.ForClassLoader.ofSystemLoader(), TypePool.Default.ReaderMode.FAST);
private static void validateAdvice(Class<?> adviceClass) {
String adviceClassName = adviceClass.getName();
ClassLoader classLoader = adviceClass.getClassLoader();
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
TypePool pool = new TypePool.Default.WithLazyResolution(TypePool.CacheProvider.NoOp.INSTANCE, ClassFileLocator.ForClassLoader.of(classLoader), TypePool.Default.ReaderMode.FAST);
TypeDescription typeDescription = pool.describe(adviceClassName).resolve();
for (MethodDescription.InDefinedShape enterAdvice : typeDescription.getDeclaredMethods().filter(isStatic().and(isAnnotatedWith(Advice.OnMethodEnter.class)))) {
validateAdviceReturnAndParameterTypes(enterAdvice);
Expand All @@ -398,7 +453,7 @@ private static void validateAdvice(String adviceClassName) {
}
}
}
if (adviceClassName.startsWith("co.elastic.apm.agent.") && adviceClassName.split("\\.").length > 6) {
if (!(classLoader instanceof ExternalPluginClassLoader) && adviceClassName.startsWith("co.elastic.apm.agent.") && adviceClassName.split("\\.").length > 6) {
throw new IllegalStateException("Indy-dispatched advice class must be at the root of the instrumentation plugin.");
}
}
Expand Down Expand Up @@ -607,6 +662,9 @@ public static void ensureInstrumented(Class<?> classToInstrument, Collection<Cla
.with(FailSafeDeclaredMethodsCompiler.INSTANCE);
AgentBuilder agentBuilder = getAgentBuilder(byteBuddy, config, logger, AgentBuilder.DescriptionStrategy.Default.HYBRID, false);
for (Class<? extends ElasticApmInstrumentation> instrumentationClass : instrumentationClasses) {
pluginClassLoaderByAdviceClass.put(
instrumentationClass.getName(),
ObjectUtils.systemClassLoaderIfNull(instrumentationClass.getClassLoader()));
ElasticApmInstrumentation apmInstrumentation = instantiate(instrumentationClass);
ElementMatcher.Junction<? super TypeDescription> typeMatcher = getTypeMatcher(classToInstrument, apmInstrumentation.getMethodMatcher(), none());
if (typeMatcher != null && isIncluded(apmInstrumentation, config)) {
Expand Down Expand Up @@ -693,15 +751,19 @@ private static ElasticApmInstrumentation tryInstantiate(Class<? extends ElasticA
}

public static ClassLoader getAgentClassLoader() {
ClassLoader agentClassLoader = ElasticApmAgent.class.getClassLoader();
if (agentClassLoader == null) {
// currently, the agent CL is the bootstrap CL in production
// but resources are not loadable from the bootstrap CL, only from the system CL
// also, we want to return a no-null CL from here
return ClassLoader.getSystemClassLoader();
}
// currently, the agent CL is the bootstrap CL in production
// but resources are not loadable from the bootstrap CL, only from the system CL
// also, we want to return a no-null CL from here
// in tests, the agent CL is the system CL
// in the future, the agent will be loaded from an isolated CL in production
return agentClassLoader;
return ObjectUtils.systemClassLoaderIfNull(ElasticApmAgent.class.getClassLoader());
}

public static ClassLoader getClassLoader(String adviceClass) {
ClassLoader classLoader = pluginClassLoaderByAdviceClass.get(adviceClass);
if (classLoader == null) {
throw new IllegalStateException("There's no mapping for key " + adviceClass);
}
return classLoader;
}
}
Loading