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

Indy plugins #1230

Merged
merged 40 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
39e25ae
Use non-inlined advices where possible
felixbarny Jun 2, 2020
aaf059c
Isolated @AssignToArgument test
felixbarny Jun 2, 2020
dc2955a
Fix AssignToFieldPostProcessorFactory by loading this on stack before…
felixbarny Jun 2, 2020
ed3109f
Add workaround for VerifyError
felixbarny Jun 2, 2020
24614be
Fix compile errors and tests
felixbarny Jun 2, 2020
c5e92f9
Add ability to assign to multiple arguments, fields and return value
felixbarny Jun 3, 2020
543f3ae
Use indy dispatcher for Servlet and JDBC plugins
felixbarny Jun 6, 2020
d80e36f
java.lang.IndyBootstrap: take this OSGi!
felixbarny Jun 7, 2020
3e68d7e
Avoid NoClassDefFoundErrors when using OSGi CLs without bootdelegatio…
felixbarny Jun 7, 2020
75b5a29
Fix IndyBootstrapDispatcher noop MethodHandle
felixbarny Jun 8, 2020
4b3aa01
Polish and documentation
felixbarny Jun 12, 2020
98d5fb0
Add test for updating class file version while retransforming
felixbarny Jun 12, 2020
019ad45
Add tests for patching class file version
felixbarny Jun 14, 2020
90d1320
Add validations for indy advices
felixbarny Jun 14, 2020
b1648ca
Remove inline=false from non-indy advices
felixbarny Jun 14, 2020
f061612
Merge remote-tracking branch 'origin/master' into indy-dispatch
felixbarny Jun 14, 2020
42d4839
Add docs, rename indyDispatch to indyPlugin
felixbarny Jun 14, 2020
6771a07
Fix tests
felixbarny Jun 14, 2020
87f324f
Update Byte Buddy
felixbarny Jun 15, 2020
8553bb6
Use MutableInt instead of AtomicInteger for CallDepth
felixbarny Jun 16, 2020
cdafeaf
Merge remote-tracking branch 'origin/master' into indy-dispatch
felixbarny Jun 17, 2020
6abbf01
Make concurrent, process, and HttpUrlConnection indy plugins
felixbarny Jun 18, 2020
8f156cf
Make @AssignTo* annotations inner classes of @AssignTo
felixbarny Jun 18, 2020
d47f768
Update Byte Buddy to 1.10.12
felixbarny Jun 18, 2020
e2d1e23
Merge remote-tracking branch 'origin/master' into indy-dispatch
felixbarny Jun 18, 2020
d4d09b2
Fix JMS tests
felixbarny Jun 19, 2020
3437036
Fix Kafka instrumentation
felixbarny Jun 19, 2020
0d10839
Fix ITs by lazily resolving type descriptions
felixbarny Jun 19, 2020
b1c69b4
Fix another test
felixbarny Jun 19, 2020
8c8b5f4
Fix bootstrap method signature
felixbarny Jun 19, 2020
960146a
Fix Payara and WebSphere tests
felixbarny Jun 22, 2020
c55474b
Fix Spring Boot tests
felixbarny Jun 22, 2020
a016876
Add bytecode examples
felixbarny Jun 25, 2020
2dec53f
Merge remote-tracking branch 'origin/master' into indy-dispatch
felixbarny Jun 25, 2020
076f116
Apply suggestions from code review
felixbarny Jun 25, 2020
eb12fb1
Implement suggestions from review
felixbarny Jun 29, 2020
ec53de6
Disallow ThreadLocals in instrumentation plugins
felixbarny Jun 29, 2020
c4bfd97
Add no shading to benefits
felixbarny Jun 30, 2020
4f56565
Allow trace_methods to instrument core java classes
felixbarny Jun 30, 2020
55d987b
Merge remote-tracking branch 'origin/master' into indy-dispatch
felixbarny Jul 1, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
Expand Down Expand Up @@ -91,14 +92,12 @@
import static net.bytebuddy.asm.Advice.ExceptionHandler.Default.PRINTING;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static net.bytebuddy.matcher.ElementMatchers.not;

Expand Down Expand Up @@ -350,32 +349,46 @@ public boolean matches(MethodDescription target) {

/**
* Validates invariants explained in {@link ElasticApmInstrumentation#indyPlugin()}
*
* @param adviceClassName the name of the advice class
*/
private static void validateAdvice(String adviceClassName) {
validateAdviceIsInlined(adviceClassName);
if (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.");
}
}

private static void validateAdviceIsInlined(String adviceClassName) {
TypePool pool = new TypePool.Default(TypePool.CacheProvider.NoOp.INSTANCE, ClassFileLocator.ForClassLoader.ofSystemLoader(), 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);

for (AnnotationDescription enter : enterAdvice.getDeclaredAnnotations().filter(ElementMatchers.annotationType(Advice.OnMethodEnter.class))) {
if (enter.prepare(Advice.OnMethodEnter.class).load().inline()) {
throw new IllegalStateException(String.format("Indy-dispatched advice %s#%s has to be declared with inline=false", adviceClassName, enterAdvice.getName()));
}
}
}
for (MethodDescription.InDefinedShape exitAdvice : typeDescription.getDeclaredMethods().filter(isStatic().and(isAnnotatedWith(Advice.OnMethodExit.class)))) {
validateAdviceReturnAndParameterTypes(exitAdvice);
if (exitAdvice.getReturnType().getTypeName().startsWith("co.elastic.apm")) {
throw new IllegalStateException("Advice return type must be visible from the bootstrap class loader and must not be an agent type.");
}
for (AnnotationDescription exit : exitAdvice.getDeclaredAnnotations().filter(ElementMatchers.annotationType(Advice.OnMethodExit.class))) {
if (exit.prepare(Advice.OnMethodExit.class).load().inline()) {
throw new IllegalStateException(String.format("Indy-dispatched advice %s#%s has to be declared with inline=false", adviceClassName, exitAdvice.getName()));
}
}
}
if (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.");
}
}

private static void validateAdviceReturnAndParameterTypes(MethodDescription.InDefinedShape advice) {
if (advice.getReturnType().getTypeName().startsWith("co.elastic.apm")) {
throw new IllegalStateException("Advice return type must not be an agent type: " + advice.toGenericString());
}
for (ParameterDescription.InDefinedShape parameter : advice.getParameters()) {
if (parameter.getName().startsWith("co.elastic.apm")) {
throw new IllegalStateException("Advice parameters must not contain an agent type: " + advice.toGenericString());
}
}
}

private static MatcherTimer getOrCreateTimer(Class<? extends ElasticApmInstrumentation> adviceClass) {
Expand Down Expand Up @@ -493,32 +506,6 @@ public Iterable<? extends List<Class<?>>> onError(int index, List<Class<?>> batc
// avoids instrumenting classes from helper class loaders
.or(any(), classLoaderWithName(ByteArrayClassLoader.ChildFirst.class.getName()))
.or(any(), classLoaderWithName("org.codehaus.groovy.runtime.callsite.CallSiteClassLoader"))
// ideally, those bootstrap classpath inclusions should be set at plugin level, see issue #952
.or(nameStartsWith("java.")
.and(
not(
nameEndsWith("URLConnection")
.or(nameStartsWith("java.util.concurrent."))
.or(named("java.lang.ProcessBuilder"))
.or(named("java.lang.ProcessImpl"))
.or(named("java.lang.Process"))
.or(named("java.lang.UNIXProcess"))
)
)
)
.or(nameStartsWith("com.sun.")
.and(
not(
nameStartsWith("com.sun.faces.")
.or(nameEndsWith("URLConnection"))
)
)
)
.or(nameStartsWith("sun")
.and(
not(nameEndsWith("URLConnection"))
)
)
.or(nameStartsWith("co.elastic.apm.agent.shaded"))
.or(nameStartsWith("org.aspectj."))
.or(nameStartsWith("org.groovy."))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,25 @@ public void onTypeMatch(TypeDescription typeDescription, ClassLoader classLoader
* </p>
* <p>
* Things to watch out for when using indy plugins:
* </p>
* <ul>
* <li>
* Set {@link Advice.OnMethodEnter#inline()} and {@link Advice.OnMethodExit#inline()} to {@code false} on all advices.
* As the {@code readOnly} flag in Byte Buddy annotations such as {@link Advice.Return#readOnly()} cannot be used with non
* {@linkplain Advice.OnMethodEnter#inline() inlined advices},
* use {@link co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignTo} and friends.
* </li>
* <li>
* Both the return type and the arguments of advice methods must no contain types from the agent.
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
* If you'd like to return a {@link Span} from an advice, for example, return an {@link Object} instead.
* When using an {@link net.bytebuddy.asm.Advice.Enter} argument on the
* {@linkplain net.bytebuddy.asm.Advice.OnMethodExit exit advice},
* that argument als has to be of type {@link Object} and you have to cast in within the method body.
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
* The reason is that the return value will become a local variable in the instrumented method.
* Due to OSGi, those methods may not have access to agent types.
* Another case is when the instrumented class is inside the bootstrap classloader.
eyalkoren marked this conversation as resolved.
Show resolved Hide resolved
* </li>
* <li>
* When an advice instruments classes in multiple class loaders, the plugin classes will be loaded form multiple class loaders.
* In order to still share state across those plugin class loaders, use {@link co.elastic.apm.agent.util.GlobalVariables} or {@link GlobalState}.
* That's necessary as a static variables are scoped to the class loader they are defined in.
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -222,14 +239,7 @@ public void onTypeMatch(TypeDescription typeDescription, ClassLoader classLoader
* As the package of the {@link #getAdviceClass()} is used as the root,
* all advices have to be at the top level of the plugin.
* </li>
* <li>
* Set {@link Advice.OnMethodEnter#inline()} and {@link Advice.OnMethodExit#inline()} to {@code false} on all advices.
* As the {@code readOnly} flag in Byte Buddy annotations such as {@link Advice.Return#readOnly()} cannot be used with non
* {@linkplain Advice.OnMethodEnter#inline() inlined advices},
* use {@link co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignTo} and friends.
* </li>
* </ul>
* </p>
*
* @return whether to load the classes of this plugin in dedicated plugin class loaders (one for each unique class loader)
* and dispatch to the {@linkplain #getAdviceClass() advice} via an {@code INVOKEDYNAMIC} instruction.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.concurrent.ConcurrentMap;

import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;

/**
Expand Down Expand Up @@ -236,7 +237,10 @@ public static ConstantCallSite bootstrap(MethodHandles.Lookup lookup,
ClassLoader pluginClassLoader = HelperClassManager.ForIndyPlugin.getOrCreatePluginClassLoader(
lookup.lookupClass().getClassLoader(),
pluginClasses,
isAnnotatedWith(named(GlobalState.class.getName())));
isAnnotatedWith(named(GlobalState.class.getName()))
// no plugin CL necessary as all types are available form bootstrap CL
// also, this plugin is used as a dependency in other plugins
.or(nameStartsWith("co.elastic.apm.agent.concurrent")));
Class<?> adviceInPluginCL = pluginClassLoader.loadClass(adviceClassName);
MethodHandle methodHandle = MethodHandles.lookup().findStatic(adviceInPluginCL, adviceMethodName, adviceMethodType);
return new ConstantCallSite(methodHandle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
Expand Down Expand Up @@ -110,6 +111,10 @@ public ElementMatcher<? super TypeDescription> getTypeMatcher() {
.and(not(nameContains("CGLIB")))
.and(not(nameContains("EnhancerBy")))
.and(not(nameContains("$Proxy")))
.and(not(nameStartsWith("java.")))
.and(not(nameStartsWith("com.sun.")))
.and(not(nameStartsWith("sun.")))
.and(not(nameStartsWith("jdk.")))
eyalkoren marked this conversation as resolved.
Show resolved Hide resolved
.and(declaresMethod(matches(methodMatcher.getMethodMatcher())));
}

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class IndyBootstrapDispatcher {

static {
try {
VOID_NOOP = MethodHandles.lookup().findStatic(IndyBootstrapDispatcher.class, "voidNoop", MethodType.methodType(void.class));
VOID_NOOP = MethodHandles.publicLookup().findStatic(IndyBootstrapDispatcher.class, "voidNoop", MethodType.methodType(void.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
/*-
* #%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.util;

import org.junit.jupiter.api.AfterEach;
Expand Down
2 changes: 0 additions & 2 deletions apm-agent-core/src/test/resources/elasticapm.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# experimental instrumentations should be active in tests
disable_instrumentations=
# in unit tests, the agent is loaded by the system class loader so we can't instrument bootstrap classes
classes_excluded_from_instrumentation=java.*,com.sun.*,sun.*
application_packages=co.elastic.apm
ship_agent_logs=false
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import org.apache.dubbo.rpc.RpcContext;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.LinkedList;
Expand Down Expand Up @@ -150,9 +149,6 @@ public void testAsyncException() throws Exception {
}

@Test
@Disabled("The new executor instrumentation doesn't wrap Runnables anymore. " +
"In this case, the Runnable is java.util.concurrent.CompletableFuture.AsyncSupply. " +
"Currently, we can't instrument java.* classes in unit tests (this will change with indy plugins).")
public void testAsyncByFuture() throws Exception {
DubboTestApi dubboTestApi = getDubboTestApi();
String arg = "hello";
Expand All @@ -168,9 +164,6 @@ public void testAsyncByFuture() throws Exception {
}

@Test
@Disabled("The new executor instrumentation doesn't wrap Runnables anymore. " +
"In this case, the Runnable is java.util.concurrent.CompletableFuture.AsyncSupply. " +
"Currently, we can't instrument java.* classes in unit tests (this will change with indy plugins).")
public void testAsyncByFutureException() throws Exception {
DubboTestApi dubboTestApi = getDubboTestApi();
String arg = "error";
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
*/
package co.elastic.apm.agent.dubbo.api.impl;

import co.elastic.apm.agent.dubbo.ExecutorServiceWrapper;
import co.elastic.apm.agent.dubbo.api.DubboTestApi;
import co.elastic.apm.agent.dubbo.api.exception.BizException;
import com.github.tomakehurst.wiremock.WireMockServer;
Expand Down Expand Up @@ -64,7 +63,7 @@ public class DubboTestApiImpl implements DubboTestApi {

public DubboTestApiImpl() {
client = new OkHttpClient();
executorService = new ExecutorServiceWrapper(Executors.newSingleThreadExecutor());
executorService = Executors.newSingleThreadExecutor();
felixbarny marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
Expand Down
Loading