From 39e25ae328f74d74f7682dcf3c98392e24bd8aef Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Tue, 2 Jun 2020 13:46:40 +0200
Subject: [PATCH 01/35] Use non-inlined advices where possible
---
.../apm/agent/bci/ElasticApmAgent.java | 15 +++-
.../bci/bytebuddy/ErrorLoggingListener.java | 18 ++++
.../postprocessor/AssignToArgument.java | 41 +++++++++
.../AssignToArgumentPostProcessorFactory.java | 64 +++++++++++++
.../postprocessor/AssignToField.java | 75 ++++++++++++++++
.../AssignToFieldPostProcessorFactory.java | 89 +++++++++++++++++++
.../postprocessor/AssignToReturn.java | 39 ++++++++
.../AssignToReturnPostProcessorFactory.java | 63 +++++++++++++
.../bytebuddy/postprocessor/package-info.java | 28 ++++++
.../apm/agent/bci/InstrumentationTest.java | 55 ++++++++++--
.../api/AbstractSpanInstrumentation.java | 68 +++++++-------
.../api/ElasticApmApiInstrumentation.java | 73 ++++++++-------
.../plugin/api/LegacySpanInstrumentation.java | 49 +++++-----
.../api/TransactionInstrumentation.java | 29 +++---
.../JmsMessageConsumerInstrumentation.java | 12 ++-
...onsumerRecordsIteratorInstrumentation.java | 10 ++-
...sumerRecordsRecordListInstrumentation.java | 10 ++-
...ConsumerRecordsRecordsInstrumentation.java | 10 ++-
...rrideClassLoaderLookupInstrumentation.java | 13 +--
.../okhttp/OkHttp3ClientInstrumentation.java | 10 +--
.../impl/ApmSpanBuilderInstrumentation.java | 12 +--
.../impl/ApmSpanInstrumentation.java | 14 +--
.../impl/ScopeManagerInstrumentation.java | 23 +++--
.../impl/SpanContextInstrumentation.java | 35 +++++---
.../CommonsExecAsyncInstrumentation.java | 10 +--
.../agent/servlet/AsyncInstrumentation.java | 12 ++-
...RequestStreamRecordingInstrumentation.java | 25 +++---
.../agent/servlet/AbstractServletTest.java | 4 +-
pom.xml | 2 +-
29 files changed, 722 insertions(+), 186 deletions(-)
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 9928294d88..24a551f9c4 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.bci;
import co.elastic.apm.agent.bci.bytebuddy.AnnotationValueOffsetMappingFactory;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgumentPostProcessorFactory;
import co.elastic.apm.agent.bci.bytebuddy.ErrorLoggingListener;
import co.elastic.apm.agent.bci.bytebuddy.FailSafeDeclaredMethodsCompiler;
import co.elastic.apm.agent.bci.bytebuddy.MatcherTimer;
@@ -32,6 +33,8 @@
import co.elastic.apm.agent.bci.bytebuddy.RootPackageCustomLocator;
import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.agent.bci.bytebuddy.SoftlyReferencingTypePoolCache;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToFieldPostProcessorFactory;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturnPostProcessorFactory;
import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentation;
import co.elastic.apm.agent.configuration.CoreConfiguration;
@@ -83,6 +86,7 @@
import static co.elastic.apm.agent.bci.bytebuddy.ClassLoaderNameMatcher.classLoaderWithName;
import static co.elastic.apm.agent.bci.bytebuddy.ClassLoaderNameMatcher.isReflectionClassLoader;
import static net.bytebuddy.asm.Advice.ExceptionHandler.Default.PRINTING;
+import static net.bytebuddy.matcher.ElementMatchers.annotationType;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
@@ -298,6 +302,10 @@ public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDesc
private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticApmTracer tracer, final ElasticApmInstrumentation instrumentation, final Logger logger, final ElementMatcher super MethodDescription> methodMatcher) {
Advice.WithCustomMapping withCustomMapping = Advice
.withCustomMapping()
+ .with(new Advice.PostProcessor.Factory.Compound(
+ new AssignToArgumentPostProcessorFactory(),
+ new AssignToReturnPostProcessorFactory(),
+ new AssignToFieldPostProcessorFactory()))
.bind(new SimpleMethodSignatureOffsetMappingFactory())
.bind(new AnnotationValueOffsetMappingFactory());
Advice.OffsetMapping.Factory> offsetMapping = instrumentation.getOffsetMapping();
@@ -410,6 +418,9 @@ private static AgentBuilder getAgentBuilder(final ByteBuddy byteBuddy, final Cor
}
}
+ final List classesExcludedFromInstrumentation = coreConfiguration.getClassesExcludedFromInstrumentation();
+ final List defaultClassesExcludedFromInstrumentation = coreConfiguration.getDefaultClassesExcludedFromInstrumentation();
+
return new AgentBuilder.Default(byteBuddy)
.with(RedefinitionStrategy.RETRANSFORMATION)
// when runtime attaching, only retransform up to 100 classes at once and sleep 100ms in-between as retransformation causes a stop-the-world pause
@@ -470,13 +481,13 @@ private static AgentBuilder getAgentBuilder(final ByteBuddy byteBuddy, final Cor
.or(new ElementMatcher.Junction.AbstractBase() {
@Override
public boolean matches(TypeDescription target) {
- return WildcardMatcher.anyMatch(coreConfiguration.getDefaultClassesExcludedFromInstrumentation(), target.getName()) != null;
+ return WildcardMatcher.anyMatch(defaultClassesExcludedFromInstrumentation, target.getName()) != null;
}
})
.or(new ElementMatcher.Junction.AbstractBase() {
@Override
public boolean matches(TypeDescription target) {
- return WildcardMatcher.anyMatch(coreConfiguration.getClassesExcludedFromInstrumentation(), target.getName()) != null;
+ return WildcardMatcher.anyMatch(classesExcludedFromInstrumentation, target.getName()) != null;
}
})
.disableClassFormatChanges();
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
index 0994bd5bbf..c362c76404 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
@@ -25,14 +25,32 @@
package co.elastic.apm.agent.bci.bytebuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.utility.JavaModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
public class ErrorLoggingListener extends AgentBuilder.Listener.Adapter {
private static final Logger logger = LoggerFactory.getLogger(ErrorLoggingListener.class);
+ @Override
+ public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
+ if (typeDescription.getName().equals("org.apache.activemq.ActiveMQMessageConsumer")) {
+ try {
+ Files.write(Paths.get("/Users/felixbarnsteiner/projects/github/elastic/apm-agent-java/ActiveMQMessageConsumer.class"), dynamicType.getBytes());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
if (throwable instanceof MinimumClassFileVersionValidator.UnsupportedClassFileVersionException) {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
new file mode 100644
index 0000000000..fe8f9c3650
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
@@ -0,0 +1,41 @@
+/*-
+ * #%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.bytebuddy.postprocessor;
+
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AssignToArgument {
+
+ int value();
+
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
new file mode 100644
index 0000000000..99c2ded174
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
@@ -0,0 +1,64 @@
+/*-
+ * #%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.bytebuddy.postprocessor;
+
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import static net.bytebuddy.matcher.ElementMatchers.annotationType;
+
+public class AssignToArgumentPostProcessorFactory implements Advice.PostProcessor.Factory {
+ @Override
+ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, boolean exit) {
+ final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToArgument.class));
+ if (!annotations.isEmpty()) {
+ final AssignToArgument assignTo = annotations.getOnly().prepare(AssignToArgument.class).load();
+ return new Advice.PostProcessor() {
+ @Override
+ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
+ final ParameterDescription param = instrumentedMethod.getParameters().get(assignTo.value());
+ final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), param.getType(), assignTo.typing());
+ if (!assign.isValid()) {
+ throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + param.getType());
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ assign,
+ MethodVariableAccess.store(param)
+ );
+ }
+
+ };
+ } else {
+ return Advice.PostProcessor.NoOp.INSTANCE;
+ }
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
new file mode 100644
index 0000000000..87e461511f
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
@@ -0,0 +1,75 @@
+/*-
+ * #%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.bytebuddy.postprocessor;
+
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AssignToField {
+ /**
+ * Returns the name of the field.
+ *
+ * @return The name of the field.
+ */
+ String value();
+
+ /**
+ * Returns the type that declares the field that should be mapped to the annotated parameter. If this property
+ * is set to {@code void}, the field is looked up implicitly within the instrumented class's class hierarchy.
+ * The value can also be set to {@link TargetType} in order to look up the type on the instrumented type.
+ *
+ * @return The type that declares the field, {@code void} if this type should be determined implicitly or
+ * {@link TargetType} for the instrumented type.
+ */
+ Class> declaringType() default Void.class;
+
+ /**
+ *
+ * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
+ * type must be equal to the type declaring the instrumented method if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
+ * If this property is set to {@code true}, the annotated parameter can be any super type of the instrumented method's declaring type.
+ *
+ *
+ * Important: This property must be set to {@code true} if the advice method is not inlined.
+ *
+ *
+ * @return {@code true} if this parameter is read-only.
+ */
+ boolean readOnly() default true;
+
+ /**
+ * The typing that should be applied when assigning the field value.
+ *
+ * @return The typing to apply upon assignment.
+ */
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
new file mode 100644
index 0000000000..15fd0816f1
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
@@ -0,0 +1,89 @@
+/*-
+ * #%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.bytebuddy.postprocessor;
+
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import static net.bytebuddy.matcher.ElementMatchers.annotationType;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+
+public class AssignToFieldPostProcessorFactory implements Advice.PostProcessor.Factory {
+ @Override
+ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, boolean exit) {
+ final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToField.class));
+ if (!annotations.isEmpty()) {
+ final AssignToField assignTo = annotations.getOnly().prepare(AssignToField.class).load();
+ return new Advice.PostProcessor() {
+ @Override
+ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
+ final FieldDescription field = getFieldLocator(instrumentedType, assignTo).locate(assignTo.value()).getField();
+
+ if (!field.isStatic() && instrumentedMethod.isStatic()) {
+ throw new IllegalStateException("Cannot read non-static field " + field + " from static method " + instrumentedMethod);
+ } else if (instrumentedMethod.isConstructor() && !exit) {
+ throw new IllegalStateException("Cannot access non-static field before calling constructor: " + instrumentedMethod);
+ }
+
+ final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), field.getType(), assignTo.typing());
+ if (!assign.isValid()) {
+ throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + field.getType());
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ assign,
+ FieldAccess.forField(field).write()
+ );
+ }
+
+ };
+ } else {
+ return Advice.PostProcessor.NoOp.INSTANCE;
+ }
+ }
+
+ private static FieldLocator getFieldLocator(TypeDescription instrumentedType, AssignToField assignTo) {
+ if (assignTo.declaringType() == Void.class) {
+ return new FieldLocator.ForClassHierarchy(instrumentedType);
+ } else {
+ final TypeDescription declaringType = TypeDescription.ForLoadedType.of(assignTo.declaringType());
+ if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) {
+ throw new IllegalStateException(declaringType + " is no super type of " + instrumentedType);
+ }
+ return new FieldLocator.ForExactType(declaringType);
+ }
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
new file mode 100644
index 0000000000..9114ed4d81
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
@@ -0,0 +1,39 @@
+/*-
+ * #%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.bytebuddy.postprocessor;
+
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AssignToReturn {
+
+ Assigner.Typing typing() default Assigner.Typing.STATIC;
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
new file mode 100644
index 0000000000..c29d4ccc97
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
@@ -0,0 +1,63 @@
+/*-
+ * #%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.bytebuddy.postprocessor;
+
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import static net.bytebuddy.matcher.ElementMatchers.annotationType;
+
+public class AssignToReturnPostProcessorFactory implements Advice.PostProcessor.Factory {
+ @Override
+ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, boolean exit) {
+ final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToReturn.class));
+ if (!annotations.isEmpty()) {
+ final AssignToReturn assignTo = annotations.getOnly().prepare(AssignToReturn.class).load();
+ return new Advice.PostProcessor() {
+ @Override
+ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
+ final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), instrumentedMethod.getReturnType(), assignTo.typing());
+ if (!assign.isValid()) {
+ throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + instrumentedMethod.getReturnType());
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ assign,
+ MethodVariableAccess.of(instrumentedMethod.getReturnType()).storeAt(argumentHandler.returned())
+ );
+ }
+ };
+ } else {
+ return Advice.PostProcessor.NoOp.INSTANCE;
+ }
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java
new file mode 100644
index 0000000000..1a873c0d7a
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java
@@ -0,0 +1,28 @@
+/*-
+ * #%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%
+ */
+@NonnullApi
+package co.elastic.apm.agent.bci.bytebuddy.postprocessor;
+
+import co.elastic.apm.agent.annotation.NonnullApi;
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index cb7db0d505..c4e3cf91ab 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -25,6 +25,8 @@
package co.elastic.apm.agent.bci;
import co.elastic.apm.agent.MockTracer;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.configuration.SpyConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
@@ -55,6 +57,7 @@ class InstrumentationTest {
private ElasticApmTracer tracer;
private ConfigurationRegistry configurationRegistry;
private CoreConfiguration coreConfig;
+ private String privateString;
@BeforeEach
void setup() {
@@ -74,6 +77,16 @@ void testIntercept() {
assertThat(interceptMe()).isEqualTo("intercepted");
}
+ @Test
+ void testFieldAccess() {
+ init(configurationRegistry, List.of(new FieldAccessInstrumentation()));
+ assignToField("@AssignToField");
+ assertThat(privateString).isEqualTo("@AssignToField");
+ }
+
+ public void assignToField(String s) {
+ }
+
@Test
void testDisabled() {
when(coreConfig.getDisabledInstrumentations()).
@@ -225,9 +238,10 @@ private String interceptMe() {
}
public static class TestInstrumentation extends ElasticApmInstrumentation {
- @Advice.OnMethodExit
- public static void onMethodExit(@Advice.Return(readOnly = false) String returnValue) {
- returnValue = "intercepted";
+ @AssignToReturn
+ @Advice.OnMethodExit(inline = false)
+ public static String onMethodExit() {
+ return "intercepted";
}
@Override
@@ -247,9 +261,10 @@ public Collection getInstrumentationGroupNames() {
}
public static class MathInstrumentation extends ElasticApmInstrumentation {
- @Advice.OnMethodExit
- public static void onMethodExit(@Advice.Return(readOnly = false) int returnValue) {
- returnValue = 42;
+ @AssignToReturn
+ @Advice.OnMethodExit(inline = false)
+ public static int onMethodExit() {
+ return 42;
}
@Override
@@ -292,12 +307,13 @@ public Collection getInstrumentationGroupNames() {
public static class SuppressExceptionInstrumentation extends ElasticApmInstrumentation {
@Advice.OnMethodEnter(suppress = Throwable.class)
- public static void onMethodEnter() {
+ public static String onMethodEnter() {
throw new RuntimeException("This exception should be suppressed");
}
+ @AssignToReturn
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
- public static void onMethodExit(@Advice.Thrown Throwable throwable) {
+ public static String onMethodExit(@Advice.Thrown Throwable throwable) {
throw new RuntimeException("This exception should be suppressed");
}
@@ -316,4 +332,27 @@ public Collection getInstrumentationGroupNames() {
return Collections.emptyList();
}
}
+
+ public static class FieldAccessInstrumentation extends ElasticApmInstrumentation {
+
+ @AssignToField("privateString")
+ @Advice.OnMethodEnter
+ public static String onEnter(@Advice.Argument(0) String s) {
+ return s;
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ }
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return ElementMatchers.named("assignToField");
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return List.of("test", "experimental");
+ }
+ }
}
diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java
index c5a8c054ea..ea70d7f527 100644
--- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java
+++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.plugin.api;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.impl.transaction.Transaction;
@@ -69,7 +70,7 @@ public SetNameInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void setName(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String name) {
context.withName(name, PRIO_USER_SUPPLIED);
@@ -82,7 +83,7 @@ public SetTypeInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void setType(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String type) {
if (context instanceof Transaction) {
@@ -99,7 +100,7 @@ public SetTypesInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void setType(@Advice.Argument(0) Object span,
@Advice.Argument(1) @Nullable String type,
@Advice.Argument(2) @Nullable String subtype,
@@ -116,10 +117,10 @@ public DoCreateSpanInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
- @Advice.Return(readOnly = false) Object result) {
- result = context.createSpan();
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Span doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
+ return context.createSpan();
}
}
@@ -129,7 +130,7 @@ public SetStartTimestampInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void setStartTimestamp(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(value = 0) long epochMicros) {
context.setStartTimestamp(epochMicros);
@@ -142,7 +143,7 @@ public EndInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void end(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
context.end();
}
@@ -154,7 +155,7 @@ public EndWithTimestampInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void end(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(value = 0) long epochMicros) {
context.end(epochMicros);
@@ -171,12 +172,13 @@ public CaptureExceptionInstrumentation() {
.and(returns(String.class)));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void captureException(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
- @Advice.Argument(0) Throwable t,
- @Advice.Return(readOnly = false) String errorId) {
- errorId = context.captureExceptionAndGetErrorId(t);
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String captureException(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
+ @Advice.Argument(0) Throwable t) {
+ return context.captureExceptionAndGetErrorId(t);
}
}
@@ -192,7 +194,7 @@ public LegacyCaptureExceptionInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static void captureException(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) Throwable t) {
context.captureException(t);
@@ -204,11 +206,11 @@ public GetIdInstrumentation() {
super(named("getId").and(takesArguments(0)));
}
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void getId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
- @Advice.Return(readOnly = false) String id) {
- id = context.getTraceContext().getId().toString();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String getId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
+ return context.getTraceContext().getId().toString();
}
}
@@ -217,11 +219,11 @@ public GetTraceIdInstrumentation() {
super(named("getTraceId").and(takesArguments(0)));
}
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
- @Advice.Return(readOnly = false) String traceId) {
- traceId = context.getTraceContext().getTraceId().toString();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
+ return context.getTraceContext().getTraceId().toString();
}
}
@@ -231,7 +233,7 @@ public AddStringLabelInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void addLabel(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) String value) {
if (value != null) {
@@ -246,7 +248,7 @@ public AddNumberLabelInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void addLabel(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) Number value) {
if (value != null) {
@@ -261,7 +263,7 @@ public AddBooleanLabelInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void addLabel(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) Boolean value) {
if (value != null) {
@@ -276,7 +278,7 @@ public ActivateInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void activate(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
context.activate();
}
@@ -287,11 +289,11 @@ public IsSampledInstrumentation() {
super(named("isSampled"));
}
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void isSampled(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
- @Advice.Return(readOnly = false) boolean sampled) {
- sampled = context.isSampled();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static boolean isSampled(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
+ return context.isSampled();
}
}
@@ -302,7 +304,7 @@ public InjectTraceHeadersInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static void injectTraceHeaders(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) MethodHandle addHeaderMethodHandle,
@Advice.Argument(1) @Nullable Object headerInjector) throws Throwable {
diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java
index 21f2159eea..aaa32d5830 100644
--- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java
+++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java
@@ -25,6 +25,8 @@
package co.elastic.apm.agent.plugin.api;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
+import co.elastic.apm.agent.impl.transaction.Transaction;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
@@ -62,12 +64,15 @@ public StartTransactionInstrumentation() {
super(named("doStartTransaction"));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- private static void doStartTransaction(@Advice.Origin Class> clazz, @Advice.Return(readOnly = false) Object transaction) {
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Object doStartTransaction(@Advice.Origin Class> clazz) {
if (tracer != null) {
- transaction = tracer.startRootTransaction(clazz.getClassLoader());
+ return tracer.startRootTransaction(clazz.getClassLoader());
}
+ return null;
}
}
@@ -77,25 +82,27 @@ public StartTransactionWithRemoteParentInstrumentation() {
super(named("doStartTransactionWithRemoteParentFunction"));
}
+ @Nullable
+ @AssignToReturn
@SuppressWarnings({"UnusedAssignment", "ParameterCanBeLocal", "unused"})
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- private static void doStartTransaction(@Advice.Origin Class> clazz,
- @Advice.Return(readOnly = false) Object transaction,
- @Advice.Argument(0) MethodHandle getFirstHeader,
- @Advice.Argument(1) @Nullable Object headerExtractor,
- @Advice.Argument(2) MethodHandle getAllHeaders,
- @Advice.Argument(3) @Nullable Object headersExtractor) {
- if (tracer != null) {
- if (headersExtractor != null) {
- HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.get(getFirstHeader, getAllHeaders);
- transaction = tracer.startChildTransaction(HeadersExtractorBridge.Extractor.of(headerExtractor, headersExtractor), headersExtractorBridge, clazz.getClassLoader());
- } else if (headerExtractor != null) {
- HeaderExtractorBridge headersExtractorBridge = HeaderExtractorBridge.get(getFirstHeader);
- transaction = tracer.startChildTransaction(headerExtractor, headersExtractorBridge, clazz.getClassLoader());
- } else {
- transaction = tracer.startRootTransaction(clazz.getClassLoader());
- }
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Transaction doStartTransaction(@Advice.Origin Class> clazz,
+ @Advice.Argument(0) MethodHandle getFirstHeader,
+ @Advice.Argument(1) @Nullable Object headerExtractor,
+ @Advice.Argument(2) MethodHandle getAllHeaders,
+ @Advice.Argument(3) @Nullable Object headersExtractor) {
+ if (tracer == null) {
+ return null;
+ }
+ if (headersExtractor != null) {
+ HeadersExtractorBridge headersExtractorBridge = HeadersExtractorBridge.get(getFirstHeader, getAllHeaders);
+ return tracer.startChildTransaction(HeadersExtractorBridge.Extractor.of(headerExtractor, headersExtractor), headersExtractorBridge, clazz.getClassLoader());
+ } else if (headerExtractor != null) {
+ HeaderExtractorBridge headersExtractorBridge = HeaderExtractorBridge.get(getFirstHeader);
+ return tracer.startChildTransaction(headerExtractor, headersExtractorBridge, clazz.getClassLoader());
+ } else {
+ return tracer.startRootTransaction(clazz.getClassLoader());
}
}
}
@@ -105,12 +112,15 @@ public CurrentTransactionInstrumentation() {
super(named("doGetCurrentTransaction"));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- private static void doGetCurrentTransaction(@Advice.Return(readOnly = false) Object transaction) {
- if (tracer != null) {
- transaction = tracer.currentTransaction();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Object doGetCurrentTransaction() {
+ if (tracer == null) {
+ return null;
}
+ return tracer.currentTransaction();
}
}
@@ -119,12 +129,15 @@ public CurrentSpanInstrumentation() {
super(named("doGetCurrentSpan"));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- private static void doGetCurrentSpan(@Advice.Return(readOnly = false) Object span) {
- if (tracer != null) {
- span = tracer.getActive();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Object doGetCurrentSpan() {
+ if (tracer == null) {
+ return null;
}
+ return tracer.getActive();
}
}
@@ -134,8 +147,8 @@ public CaptureExceptionInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void captureException(@Advice.Origin Class> clazz, @Advice.Argument(0) @Nullable Throwable e) {
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static void captureException(@Advice.Origin Class> clazz, @Advice.Argument(0) @Nullable Throwable e) {
if (tracer != null) {
tracer.captureAndReportException(e, clazz.getClassLoader());
}
diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java
index 8fdc42e407..f8d98bcc5b 100644
--- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java
+++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/LegacySpanInstrumentation.java
@@ -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
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.plugin.api;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.Span;
import net.bytebuddy.asm.Advice;
@@ -33,6 +34,8 @@
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
+import javax.annotation.Nullable;
+
import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_USER_SUPPLIED;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.named;
@@ -99,11 +102,11 @@ public DoCreateSpanInstrumentation() {
super(named("doCreateSpan"));
}
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit
- public static void doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span,
- @Advice.Return(readOnly = false) Object result) {
- result = span.createSpan();
+ @Advice.OnMethodExit(inline = false)
+ public static Span doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span) {
+ return span.createSpan();
}
}
@@ -126,7 +129,7 @@ public CaptureExceptionInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodExit
+ @Advice.OnMethodExit(inline = false)
public static void doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span,
@Advice.Argument(0) Throwable t) {
span.captureException(t);
@@ -138,13 +141,15 @@ public GetIdInstrumentation() {
super(named("getId").and(takesArguments(0)));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit
- public static void getId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span,
- @Advice.Return(readOnly = false) String id) {
- if (tracer != null) {
- id = span.getTraceContext().getId().toString();
+ @Advice.OnMethodExit(inline = false)
+ public static String getId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span) {
+ if (tracer == null) {
+ return null;
}
+ return span.getTraceContext().getId().toString();
}
}
@@ -153,13 +158,15 @@ public GetTraceIdInstrumentation() {
super(named("getTraceId").and(takesArguments(0)));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit
- public static void getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span,
- @Advice.Return(readOnly = false) String traceId) {
- if (tracer != null) {
- traceId = span.getTraceContext().getTraceId().toString();
+ @Advice.OnMethodExit(inline = false)
+ public static String getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span) {
+ if (tracer == null) {
+ return null;
}
+ return span.getTraceContext().getTraceId().toString();
}
}
@@ -193,11 +200,11 @@ public IsSampledInstrumentation() {
super(named("isSampled"));
}
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit
- public static void addTag(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span,
- @Advice.Return(readOnly = false) boolean sampled) {
- sampled = span.isSampled();
+ @Advice.OnMethodExit(inline = false)
+ public static boolean addTag(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> span) {
+ return span.isSampled();
}
}
}
diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java
index f23c3817c3..11ed9a19cb 100644
--- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java
+++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.plugin.api;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.transaction.TraceContext;
import co.elastic.apm.agent.impl.transaction.Transaction;
import net.bytebuddy.asm.Advice;
@@ -64,7 +65,7 @@ public SetUserInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void setUser(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
@Advice.Argument(0) String id, @Advice.Argument(1) String email, @Advice.Argument(2) String username) {
transaction.setUser(id, email, username);
@@ -76,17 +77,19 @@ public EnsureParentIdInstrumentation() {
super(named("ensureParentId"));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
- @Advice.Return(readOnly = false) String spanId) {
- if (tracer != null) {
- final TraceContext traceContext = transaction.getTraceContext();
- if (traceContext.getParentId().isEmpty()) {
- traceContext.getParentId().setToRandomValue();
- }
- spanId = traceContext.getParentId().toString();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction) {
+ if (tracer == null) {
+ return null;
}
+ final TraceContext traceContext = transaction.getTraceContext();
+ if (traceContext.getParentId().isEmpty()) {
+ traceContext.getParentId().setToRandomValue();
+ }
+ return traceContext.getParentId().toString();
}
}
@@ -95,8 +98,8 @@ public SetResultInstrumentation() {
super(named("setResult"));
}
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static void ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
@Advice.Argument(0) String result) {
transaction.withResult(result);
}
@@ -108,7 +111,7 @@ public AddCustomContextInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void addCustomContext(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) Object value) {
if (value != null ) {
diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java
index dc5b2ce007..8d2233b829 100644
--- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java
+++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java
@@ -25,6 +25,8 @@
package co.elastic.apm.agent.jms;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.configuration.MessagingConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
@@ -261,15 +263,19 @@ public Class> getAdviceClass() {
public static class ListenerWrappingAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- public static void beforeSetListener(@Advice.Argument(value = 0, readOnly = false) @Nullable MessageListener original) {
+ @Nullable
+ @AssignToArgument(0)
+ @Advice.OnMethodEnter(inline = false)
+ public static MessageListener beforeSetListener(@Advice.Argument(0) @Nullable MessageListener original) {
//noinspection ConstantConditions - the Advice must be invoked only if the BaseJmsInstrumentation constructor was invoked
JmsInstrumentationHelper helper =
jmsInstrHelperManager.getForClassLoaderOfClass(MessageListener.class);
if (helper != null) {
- original = helper.wrapLambda(original);
+ return helper.wrapLambda(original);
}
+ return original;
}
+
}
}
}
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java
index 626be77a65..f8b8056d25 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.kafka;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.kafka.helper.KafkaInstrumentationHeadersHelper;
import net.bytebuddy.asm.Advice;
@@ -66,10 +67,12 @@ public Class> getAdviceClass() {
@SuppressWarnings("rawtypes")
public static class ConsumerRecordsAdvice {
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
- public static void wrapIterator(@Nullable @Advice.Return(readOnly = false) Iterator iterator) {
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ public static Iterator wrapIterator(@Nullable @Advice.Return Iterator iterator) {
if (tracer == null || !tracer.isRunning() || tracer.currentTransaction() != null) {
- return;
+ return iterator;
}
//noinspection ConstantConditions,rawtypes
@@ -78,6 +81,7 @@ public static void wrapIterator(@Nullable @Advice.Return(readOnly = false) Itera
if (iterator != null && kafkaInstrumentationHelper != null) {
iterator = kafkaInstrumentationHelper.wrapConsumerRecordIterator(iterator);
}
+ return iterator;
}
}
}
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java
index 7e234b3dbd..003f84d623 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.kafka;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.kafka.helper.KafkaInstrumentationHeadersHelper;
import net.bytebuddy.asm.Advice;
@@ -67,10 +68,12 @@ public Class> getAdviceClass() {
@SuppressWarnings("rawtypes")
public static class ConsumerRecordsAdvice {
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
- public static void wrapRecordList(@Nullable @Advice.Return(readOnly = false) List list) {
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ public static List wrapRecordList(@Nullable @Advice.Return List list) {
if (tracer == null || !tracer.isRunning() || tracer.currentTransaction() != null) {
- return;
+ return list;
}
//noinspection ConstantConditions,rawtypes
@@ -79,6 +82,7 @@ public static void wrapRecordList(@Nullable @Advice.Return(readOnly = false) Lis
if (list != null && kafkaInstrumentationHelper != null) {
list = kafkaInstrumentationHelper.wrapConsumerRecordList(list);
}
+ return list;
}
}
}
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java
index 043e304695..940cdcad1c 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.kafka;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.kafka.helper.KafkaInstrumentationHeadersHelper;
import net.bytebuddy.asm.Advice;
@@ -65,10 +66,12 @@ public Class> getAdviceClass() {
@SuppressWarnings("rawtypes")
public static class ConsumerRecordsAdvice {
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
- public static void wrapIterable(@Nullable @Advice.Return(readOnly = false) Iterable iterable) {
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ public static Iterable wrapIterable(@Nullable @Advice.Return Iterable iterable) {
if (tracer == null || !tracer.isRunning() || tracer.currentTransaction() != null) {
- return;
+ return iterable;
}
//noinspection ConstantConditions,rawtypes
@@ -77,6 +80,7 @@ public static void wrapIterable(@Nullable @Advice.Return(readOnly = false) Itera
if (iterable != null && kafkaInstrumentationHelper != null) {
iterable = kafkaInstrumentationHelper.wrapConsumerRecordIterable(iterable);
}
+ return iterable;
}
}
}
diff --git a/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java b/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java
index b6cba0648c..3af4d76037 100644
--- a/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java
+++ b/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java
@@ -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
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.mule4;
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
@@ -77,9 +78,10 @@ public Class> getAdviceClass() {
}
public static class Mule4OverrideClassLoaderLookupAdvice {
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
- public static void makeParentOnlyForAgentClasses(@Advice.Argument(0) @Nullable final String packageName,
- @Advice.Return(readOnly = false) LookupStrategy lookupStrategy) {
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static LookupStrategy makeParentOnlyForAgentClasses(@Advice.Argument(0) @Nullable final String packageName,
+ @Advice.Return LookupStrategy lookupStrategy) {
// Until instrumentation mechanism is initiated, agent classes get loaded from the launching class loader.
// Whenever flows are invoked with the visibility of this class loader's classpath, we can't let other agent
@@ -90,6 +92,7 @@ public static void makeParentOnlyForAgentClasses(@Advice.Argument(0) @Nullable f
Mule4OverrideClassLoaderLookupInstrumentation.class.getClassLoader() == null) {
lookupStrategy = new DelegateOnlyLookupStrategy(ClassLoader.getSystemClassLoader());
}
+ return lookupStrategy;
}
}
}
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
index 8afae3a240..dd7a78523f 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
@@ -92,17 +92,17 @@ private static void onBeforeExecute( @Advice.FieldValue(value = "originalRequest
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Return @Nullable okhttp3.Response response,
- @Advice.Local("span") @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
- if (span != null) {
+ final AbstractSpan> active = getActive();
+ if (active != null && active.isExit()) {
try {
if (response != null) {
int statusCode = response.code();
- span.getContext().getHttp().withStatusCode(statusCode);
+ ((Span) active).getContext().getHttp().withStatusCode(statusCode);
}
- span.captureException(t);
+ active.captureException(t);
} finally {
- span.deactivate().end();
+ active.deactivate().end();
}
}
}
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java
index 443edccbe8..03c7f9f0ff 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.opentracing.impl;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.sampling.ConstantSampler;
import co.elastic.apm.agent.impl.sampling.Sampler;
@@ -69,15 +70,16 @@ public CreateSpanInstrumentation() {
super(named("createSpan"));
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void createSpan(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> parentContext,
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Object createSpan(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> parentContext,
@Advice.Origin Class> spanBuilderClass,
@Advice.FieldValue(value = "tags") Map tags,
@Advice.FieldValue(value = "operationName") String operationName,
@Advice.FieldValue(value = "microseconds") long microseconds,
- @Advice.Argument(1) @Nullable Iterable> baggage,
- @Advice.Return(readOnly = false) Object span) {
- span = doCreateTransactionOrSpan(parentContext, tags, operationName, microseconds, baggage, spanBuilderClass.getClassLoader());
+ @Advice.Argument(1) @Nullable Iterable> baggage) {
+ return doCreateTransactionOrSpan(parentContext, tags, operationName, microseconds, baggage, spanBuilderClass.getClassLoader());
}
@Nullable
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
index 602dffedfc..22d716cc77 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.opentracing.impl;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.context.web.ResultUtil;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.Span;
@@ -71,7 +72,7 @@ public FinishInstrumentation() {
}
@Advice.OnMethodEnter(suppress = Throwable.class)
- private static void finishInternal(@Advice.FieldValue(value = "dispatcher", readOnly = false, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
+ private static void finishInternal(@Advice.FieldValue(value = "dispatcher", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
@Advice.Argument(0) long finishMicros) {
if (span != null) {
doFinishInternal(span, finishMicros);
@@ -290,12 +291,11 @@ public GetTraceContextInstrumentation() {
super(named("getTraceContext"));
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void getTraceContext(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> abstractSpan,
- @Advice.Return(readOnly = false) Object traceContext) {
- if (abstractSpan != null) {
- traceContext = abstractSpan;
- }
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Object getTraceContext(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> abstractSpan) {
+ return abstractSpan;
}
}
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java
index 8dff44aaba..60dcea73ab 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.opentracing.impl;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
@@ -75,12 +76,15 @@ public CurrentSpanInstrumentation() {
super(named("getCurrentSpan"));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void getCurrentSpan(@Advice.Return(readOnly = false) Object span) {
- if (tracer != null) {
- span = tracer.getActive();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Object getCurrentSpan() {
+ if (tracer == null) {
+ return null;
}
+ return tracer.getActive();
}
}
@@ -91,12 +95,15 @@ public CurrentTraceContextInstrumentation() {
super(named("getCurrentTraceContext"));
}
+ @Nullable
+ @AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void getCurrentTraceContext(@Advice.Return(readOnly = false) Object traceContext) {
- if (tracer != null) {
- traceContext = tracer.getActive();
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Object getCurrentTraceContext() {
+ if (tracer == null) {
+ return null;
}
+ return tracer.getActive();
}
}
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java
index 2430081bcf..6c3537e81b 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.opentracing.impl;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
@@ -69,13 +70,15 @@ public BaggageItemsInstrumentation() {
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void baggageItems(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext,
- @Advice.Return(readOnly = false) Iterable> baggage) {
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static Iterable> baggageItems(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext) {
if (traceContext != null) {
- baggage = doGetBaggage(traceContext);
+ return doGetBaggage(traceContext);
} else {
logger.info("The traceContext is null");
+ return null;
}
}
@@ -93,12 +96,14 @@ public ToTraceIdInstrumentation() {
super(named("toTraceId"));
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void toTraceId(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext,
- @Advice.Return(readOnly = false) String traceId) {
- if (traceContext != null) {
- traceId = traceContext.getTraceContext().getTraceId().toString();
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String toTraceId(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext) {
+ if (traceContext == null) {
+ return null;
}
+ return traceContext.getTraceContext().getTraceId().toString();
}
}
@@ -108,12 +113,14 @@ public ToSpanIdInstrumentation() {
super(named("toSpanId"));
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void toTraceId(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext,
- @Advice.Return(readOnly = false) String spanId) {
- if (traceContext != null) {
- spanId = traceContext.getTraceContext().getId().toString();
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String toTraceId(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext) {
+ if (traceContext == null) {
+ return null;
}
+ return traceContext.getTraceContext().getId().toString();
}
}
}
diff --git a/apm-agent-plugins/apm-process-plugin/src/main/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentation.java b/apm-agent-plugins/apm-process-plugin/src/main/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentation.java
index 439eeb039a..4d03ae5909 100644
--- a/apm-agent-plugins/apm-process-plugin/src/main/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-process-plugin/src/main/java/co/elastic/apm/agent/process/CommonsExecAsyncInstrumentation.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.process;
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
@@ -87,15 +88,14 @@ public Class> getAdviceClass() {
public static final class CommonsExecAdvice {
+ @AssignToArgument(0)
@Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onEnter(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) {
+ private static Runnable onEnter(Runnable runnable) {
if (tracer == null || tracer.getActive() == null) {
- return;
+ return runnable;
}
// context propagation is done by wrapping existing runnable argument
-
- //noinspection UnusedAssignment
- runnable = tracer.getActive().withActive(runnable);
+ return tracer.getActive().withActive(runnable);
}
}
}
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java
index 2261d60c30..1e717c74ca 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java
@@ -26,6 +26,7 @@
import co.elastic.apm.agent.bci.HelperClassManager;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Transaction;
import net.bytebuddy.asm.Advice;
@@ -179,8 +180,10 @@ public Class> getAdviceClass() {
@VisibleForAdvice
public static class AsyncContextStartAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onEnterAsyncContextStart(@Advice.Argument(value = 0, readOnly = false) @Nullable Runnable runnable) {
+ @Nullable
+ @AssignToArgument(0)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Runnable onEnterAsyncContextStart(@Advice.Argument(0) @Nullable Runnable runnable) {
if (tracer != null && runnable != null && tracer.isWrappingAllowedOnThread()) {
final Transaction transaction = tracer.currentTransaction();
if (transaction != null) {
@@ -188,10 +191,11 @@ private static void onEnterAsyncContextStart(@Advice.Argument(value = 0, readOnl
tracer.avoidWrappingOnThread();
}
}
+ return runnable;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Exception.class)
- private static void onExitAsyncContextStart() {
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Exception.class, inline = false)
+ public static void onExitAsyncContextStart() {
if (tracer != null) {
tracer.allowWrappingOnThread();
}
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java
index e20846c0b6..f4489736af 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java
@@ -26,6 +26,7 @@
import co.elastic.apm.agent.bci.HelperClassManager;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.context.Request;
import co.elastic.apm.agent.impl.transaction.Transaction;
@@ -105,25 +106,27 @@ protected Boolean initialValue() {
}
};
- @Advice.OnMethodEnter(suppress = Throwable.class)
- public static void onReadEnter(@Advice.This Object thiz,
- @Advice.Local("transaction") Transaction transaction,
- @Advice.Local("nested") boolean nested) {
- nested = nestedThreadLocal.get();
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static boolean onReadEnter(@Advice.This Object thiz) {
+ boolean nested = nestedThreadLocal.get();
nestedThreadLocal.set(Boolean.TRUE);
+ return nested;
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void afterGetInputStream(@Advice.Return(readOnly = false) ServletInputStream inputStream,
- @Advice.Local("nested") boolean nested) {
- if (nested || tracer == null || wrapperHelperClassManager == null) {
- return;
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static ServletInputStream afterGetInputStream(@Advice.Return ServletInputStream inputStream,
+ @Advice.Enter boolean nested) {
+ if (nested == Boolean.TRUE || tracer == null || wrapperHelperClassManager == null) {
+ return inputStream;
}
try {
final Transaction transaction = tracer.currentTransaction();
// only wrap if the body buffer has been initialized via ServletTransactionHelper.startCaptureBody
if (transaction != null && transaction.getContext().getRequest().getBodyBuffer() != null) {
- inputStream = wrapperHelperClassManager.getForClassLoaderOfClass(inputStream.getClass()).wrap(transaction.getContext().getRequest(), inputStream);
+ return wrapperHelperClassManager.getForClassLoaderOfClass(inputStream.getClass()).wrap(transaction.getContext().getRequest(), inputStream);
+ } else {
+ return inputStream;
}
} finally {
nestedThreadLocal.set(Boolean.FALSE);
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java
index 18a6f3d05c..574587e1cd 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/test/java/co/elastic/apm/agent/servlet/AbstractServletTest.java
@@ -68,8 +68,8 @@ void initServerAndClient() throws Exception {
httpClient = new OkHttpClient.Builder()
// set to 0 for debugging
- .readTimeout(10, TimeUnit.SECONDS)
- .connectTimeout(10, TimeUnit.SECONDS)
+ .readTimeout(0, TimeUnit.SECONDS)
+ .connectTimeout(0, TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.build();
}
diff --git a/pom.xml b/pom.xml
index dd7e7e7ad4..ed9d02d277 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,7 +96,7 @@
5.0.15.RELEASE9.4.11.v201806050.1.19
- 1.10.8
+ 1.10.11-SNAPSHOT1.17
From aaf059ca7474c72bf201f39d829aa6b0b79eea31 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Tue, 2 Jun 2020 14:35:41 +0200
Subject: [PATCH 02/35] Isolated @AssignToArgument test
---
.../bci/bytebuddy/ErrorLoggingListener.java | 11 ------
.../apm/agent/bci/InstrumentationTest.java | 34 +++++++++++++++++++
2 files changed, 34 insertions(+), 11 deletions(-)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
index c362c76404..71a3da1011 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
@@ -40,17 +40,6 @@ public class ErrorLoggingListener extends AgentBuilder.Listener.Adapter {
private static final Logger logger = LoggerFactory.getLogger(ErrorLoggingListener.class);
- @Override
- public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, boolean loaded, DynamicType dynamicType) {
- if (typeDescription.getName().equals("org.apache.activemq.ActiveMQMessageConsumer")) {
- try {
- Files.write(Paths.get("/Users/felixbarnsteiner/projects/github/elastic/apm-agent-java/ActiveMQMessageConsumer.class"), dynamicType.getBytes());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
if (throwable instanceof MinimumClassFileVersionValidator.UnsupportedClassFileVersionException) {
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index c4e3cf91ab..a8318edc43 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.bci;
import co.elastic.apm.agent.MockTracer;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.configuration.CoreConfiguration;
@@ -87,6 +88,16 @@ void testFieldAccess() {
public void assignToField(String s) {
}
+ @Test
+ void testAssignToArgument() {
+ init(configurationRegistry, List.of(new AssignToArgumentInstrumentation()));
+ assertThat(assignToArgument("foo")).isEqualTo("foo@AssignToArgument");
+ }
+
+ public String assignToArgument(String s) {
+ return s;
+ }
+
@Test
void testDisabled() {
when(coreConfig.getDisabledInstrumentations()).
@@ -355,4 +366,27 @@ public Collection getInstrumentationGroupNames() {
return List.of("test", "experimental");
}
}
+
+ public static class AssignToArgumentInstrumentation extends ElasticApmInstrumentation {
+
+ @AssignToArgument(0)
+ @Advice.OnMethodEnter
+ public static String onEnter(@Advice.Argument(0) String s) {
+ return s + "@AssignToArgument";
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ }
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return ElementMatchers.named("assignToArgument");
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return List.of("test", "experimental");
+ }
+ }
}
From dc2955a7c79af74154502e67594933a91c1efceb Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Tue, 2 Jun 2020 17:40:55 +0200
Subject: [PATCH 03/35] Fix AssignToFieldPostProcessorFactory by loading this
on stack before writing to field
---
.../postprocessor/AssignToFieldPostProcessorFactory.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
index 15fd0816f1..a214997a45 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
@@ -27,9 +27,7 @@
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.field.FieldDescription;
-import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
-import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
@@ -39,7 +37,6 @@
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import static net.bytebuddy.matcher.ElementMatchers.annotationType;
-import static net.bytebuddy.matcher.ElementMatchers.named;
public class AssignToFieldPostProcessorFactory implements Advice.PostProcessor.Factory {
@Override
@@ -63,6 +60,7 @@ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescrip
throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + field.getType());
}
return new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
assign,
FieldAccess.forField(field).write()
From ed3109fe1585455f21c2b2e3bf17154825de38b3 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Tue, 2 Jun 2020 17:50:25 +0200
Subject: [PATCH 04/35] Add workaround for VerifyError
---
.../apm/agent/jms/JmsMessageConsumerInstrumentation.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java
index 8d2233b829..1be142a74e 100644
--- a/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java
+++ b/apm-agent-plugins/apm-jms-plugin/src/main/java/co/elastic/apm/agent/jms/JmsMessageConsumerInstrumentation.java
@@ -276,6 +276,11 @@ public static MessageListener beforeSetListener(@Advice.Argument(0) @Nullable Me
return original;
}
+ @Advice.OnMethodExit
+ private static void onExit() {
+ // workaround for https://github.com/raphw/byte-buddy/issues/874#issuecomment-637616829
+ }
+
}
}
}
From 24614beffc0cbd205c7213d28edc96696a56134f Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Tue, 2 Jun 2020 18:41:49 +0200
Subject: [PATCH 05/35] Fix compile errors and tests
---
.../postprocessor/AssignToArgumentPostProcessorFactory.java | 2 +-
.../postprocessor/AssignToFieldPostProcessorFactory.java | 2 +-
.../postprocessor/AssignToReturnPostProcessorFactory.java | 4 +---
.../java/co/elastic/apm/agent/bci/InstrumentationTest.java | 2 ++
.../apm-redis-plugin/apm-redisson-plugin/pom.xml | 6 ++++++
5 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
index 99c2ded174..0bcecce540 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
@@ -37,7 +37,7 @@
public class AssignToArgumentPostProcessorFactory implements Advice.PostProcessor.Factory {
@Override
- public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, boolean exit) {
+ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, final boolean exit) {
final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToArgument.class));
if (!annotations.isEmpty()) {
final AssignToArgument assignTo = annotations.getOnly().prepare(AssignToArgument.class).load();
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
index a214997a45..def042d434 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
@@ -40,7 +40,7 @@
public class AssignToFieldPostProcessorFactory implements Advice.PostProcessor.Factory {
@Override
- public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, boolean exit) {
+ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, final boolean exit) {
final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToField.class));
if (!annotations.isEmpty()) {
final AssignToField assignTo = annotations.getOnly().prepare(AssignToField.class).load();
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
index c29d4ccc97..35ccd4af7f 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
@@ -27,18 +27,16 @@
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.annotation.AnnotationList;
import net.bytebuddy.description.method.MethodDescription;
-import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
-import net.bytebuddy.implementation.bytecode.member.MethodReturn;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import static net.bytebuddy.matcher.ElementMatchers.annotationType;
public class AssignToReturnPostProcessorFactory implements Advice.PostProcessor.Factory {
@Override
- public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, boolean exit) {
+ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, final boolean exit) {
final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToReturn.class));
if (!annotations.isEmpty()) {
final AssignToReturn assignTo = annotations.getOnly().prepare(AssignToReturn.class).load();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index a8318edc43..c92a7f32c9 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -42,6 +42,7 @@
import org.apache.commons.math.util.MathUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.stagemonitor.configuration.ConfigurationRegistry;
@@ -205,6 +206,7 @@ void testDontInstrumentOldClassFileVersions() {
}
@Test
+ @Disabled("this is currently a limitation in Byte Buddy")
void testSuppressException() {
ElasticApmAgent.initInstrumentation(tracer,
ByteBuddyAgent.install(),
diff --git a/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml b/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml
index bb02da0325..e1c00fb9c8 100644
--- a/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml
+++ b/apm-agent-plugins/apm-redis-plugin/apm-redisson-plugin/pom.xml
@@ -34,6 +34,12 @@
redisson3.12.3provided
+
+
+ net.bytebuddy
+ byte-buddy
+
+
From c5e92f9414a6e35acec8f22a86b5caaebf1b15ed Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Wed, 3 Jun 2020 16:38:45 +0200
Subject: [PATCH 06/35] Add ability to assign to multiple arguments, fields and
return value
Migrate more plugins
---
.../apm/agent/bci/ElasticApmAgent.java | 10 +-
.../agent/bci/ElasticApmInstrumentation.java | 23 ++
.../bci/bytebuddy/postprocessor/AssignTo.java | 14 ++
.../postprocessor/AssignToArgument.java | 6 +-
.../AssignToArgumentPostProcessorFactory.java | 64 -----
.../postprocessor/AssignToField.java | 16 +-
.../AssignToPostProcessorFactory.java | 232 ++++++++++++++++++
.../postprocessor/AssignToReturn.java | 6 +-
.../agent/util/RemoveOnGetThreadLocal.java | 22 ++
.../apm/agent/bci/InstrumentationTest.java | 107 ++++++++
.../ApacheHttpAsyncClientInstrumentation.java | 96 ++++----
...asticsearchClientAsyncInstrumentation.java | 46 ++--
...lasticsearchClientSyncInstrumentation.java | 31 +--
...asticsearchClientAsyncInstrumentation.java | 42 ++--
...lasticsearchClientSyncInstrumentation.java | 28 ++-
.../kafka/KafkaProducerInstrumentation.java | 35 ++-
.../KafkaProducerHeadersInstrumentation.java | 6 +-
.../OkHttp3ClientAsyncInstrumentation.java | 29 ++-
.../okhttp/OkHttp3ClientInstrumentation.java | 57 ++---
.../OkHttpClientAsyncInstrumentation.java | 29 ++-
.../okhttp/OkHttpClientInstrumentation.java | 51 ++--
.../ExternalSpanContextInstrumentation.java | 56 +++--
22 files changed, 679 insertions(+), 327 deletions(-)
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
delete mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 24a551f9c4..817ba58012 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -25,7 +25,6 @@
package co.elastic.apm.agent.bci;
import co.elastic.apm.agent.bci.bytebuddy.AnnotationValueOffsetMappingFactory;
-import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgumentPostProcessorFactory;
import co.elastic.apm.agent.bci.bytebuddy.ErrorLoggingListener;
import co.elastic.apm.agent.bci.bytebuddy.FailSafeDeclaredMethodsCompiler;
import co.elastic.apm.agent.bci.bytebuddy.MatcherTimer;
@@ -33,8 +32,7 @@
import co.elastic.apm.agent.bci.bytebuddy.RootPackageCustomLocator;
import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.agent.bci.bytebuddy.SoftlyReferencingTypePoolCache;
-import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToFieldPostProcessorFactory;
-import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturnPostProcessorFactory;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToPostProcessorFactory;
import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentation;
import co.elastic.apm.agent.configuration.CoreConfiguration;
@@ -86,7 +84,6 @@
import static co.elastic.apm.agent.bci.bytebuddy.ClassLoaderNameMatcher.classLoaderWithName;
import static co.elastic.apm.agent.bci.bytebuddy.ClassLoaderNameMatcher.isReflectionClassLoader;
import static net.bytebuddy.asm.Advice.ExceptionHandler.Default.PRINTING;
-import static net.bytebuddy.matcher.ElementMatchers.annotationType;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
@@ -302,10 +299,7 @@ public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDesc
private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticApmTracer tracer, final ElasticApmInstrumentation instrumentation, final Logger logger, final ElementMatcher super MethodDescription> methodMatcher) {
Advice.WithCustomMapping withCustomMapping = Advice
.withCustomMapping()
- .with(new Advice.PostProcessor.Factory.Compound(
- new AssignToArgumentPostProcessorFactory(),
- new AssignToReturnPostProcessorFactory(),
- new AssignToFieldPostProcessorFactory()))
+ .with(new AssignToPostProcessorFactory())
.bind(new SimpleMethodSignatureOffsetMappingFactory())
.bind(new AnnotationValueOffsetMappingFactory());
Advice.OffsetMapping.Factory> offsetMapping = instrumentation.getOffsetMapping();
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
index 236a7e7a56..c4db3392a3 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
@@ -80,6 +80,29 @@ public static AbstractSpan> getActive() {
return null;
}
+ @Nullable
+ @VisibleForAdvice
+ public static Span getActiveSpan() {
+ if (tracer != null) {
+ final AbstractSpan> active = tracer.getActive();
+ if (active instanceof Span) {
+ return (Span) active;
+ }
+ }
+ return null;
+ }
+
+
+ @Nullable
+ @VisibleForAdvice
+ public static Span getActiveExitSpan() {
+ final Span span = getActiveSpan();
+ if (span != null && span.isExit()) {
+ return span;
+ }
+ return null;
+ }
+
@Nullable
@VisibleForAdvice
public static Span createExitSpan() {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
new file mode 100644
index 0000000000..07c7e78796
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
@@ -0,0 +1,14 @@
+package co.elastic.apm.agent.bci.bytebuddy.postprocessor;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AssignTo {
+ AssignToArgument[] arguments() default {};
+ AssignToField[] fields() default {};
+ AssignToReturn[] returns() default {};
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
index fe8f9c3650..c6b42ba9b5 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
@@ -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
@@ -37,5 +37,7 @@
int value();
+ int index() default -1;
+
Assigner.Typing typing() default Assigner.Typing.STATIC;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
deleted file mode 100644
index 0bcecce540..0000000000
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgumentPostProcessorFactory.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*-
- * #%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.bytebuddy.postprocessor;
-
-import net.bytebuddy.asm.Advice;
-import net.bytebuddy.description.annotation.AnnotationList;
-import net.bytebuddy.description.method.MethodDescription;
-import net.bytebuddy.description.method.ParameterDescription;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.implementation.bytecode.StackManipulation;
-import net.bytebuddy.implementation.bytecode.assign.Assigner;
-import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
-
-import static net.bytebuddy.matcher.ElementMatchers.annotationType;
-
-public class AssignToArgumentPostProcessorFactory implements Advice.PostProcessor.Factory {
- @Override
- public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, final boolean exit) {
- final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToArgument.class));
- if (!annotations.isEmpty()) {
- final AssignToArgument assignTo = annotations.getOnly().prepare(AssignToArgument.class).load();
- return new Advice.PostProcessor() {
- @Override
- public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
- final ParameterDescription param = instrumentedMethod.getParameters().get(assignTo.value());
- final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), param.getType(), assignTo.typing());
- if (!assign.isValid()) {
- throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + param.getType());
- }
- return new StackManipulation.Compound(
- MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
- assign,
- MethodVariableAccess.store(param)
- );
- }
-
- };
- } else {
- return Advice.PostProcessor.NoOp.INSTANCE;
- }
- }
-}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
index 87e461511f..7cbb6e2958 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
@@ -42,6 +42,8 @@
*/
String value();
+ int index() default -1;
+
/**
* Returns the type that declares the field that should be mapped to the annotated parameter. If this property
* is set to {@code void}, the field is looked up implicitly within the instrumented class's class hierarchy.
@@ -52,20 +54,6 @@
*/
Class> declaringType() default Void.class;
- /**
- *
- * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated
- * type must be equal to the type declaring the instrumented method if the typing is not also set to {@link Assigner.Typing#DYNAMIC}.
- * If this property is set to {@code true}, the annotated parameter can be any super type of the instrumented method's declaring type.
- *
- *
- * Important: This property must be set to {@code true} if the advice method is not inlined.
- *
- *
- * @return {@code true} if this parameter is read-only.
- */
- boolean readOnly() default true;
-
/**
* The typing that should be applied when assigning the field value.
*
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java
new file mode 100644
index 0000000000..7c25f2f166
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java
@@ -0,0 +1,232 @@
+/*-
+ * #%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.bytebuddy.postprocessor;
+
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.annotation.AnnotationDescription;
+import net.bytebuddy.description.annotation.AnnotationList;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.ParameterDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.TargetType;
+import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.Assigner;
+import net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.annotationType;
+
+public class AssignToPostProcessorFactory implements Advice.PostProcessor.Factory {
+ private static FieldLocator getFieldLocator(TypeDescription instrumentedType, AssignToField assignTo) {
+ if (assignTo.declaringType() == Void.class) {
+ return new FieldLocator.ForClassHierarchy(instrumentedType);
+ } else {
+ final TypeDescription declaringType = TypeDescription.ForLoadedType.of(assignTo.declaringType());
+ if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) {
+ throw new IllegalStateException(declaringType + " is no super type of " + instrumentedType);
+ }
+ return new FieldLocator.ForExactType(declaringType);
+ }
+ }
+
+ @Override
+ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, final boolean exit) {
+ final AnnotationList annotations = adviceMethod.getDeclaredAnnotations()
+ .filter(annotationType(AssignToArgument.class)
+ .or(annotationType(AssignToField.class))
+ .or(annotationType(AssignToReturn.class))
+ .or(annotationType(AssignTo.class)));
+ if (annotations.isEmpty()) {
+ return Advice.PostProcessor.NoOp.INSTANCE;
+ }
+ final List postProcessors = new ArrayList<>();
+ for (AnnotationDescription annotation : annotations) {
+ if (annotation.getAnnotationType().represents(AssignToArgument.class)) {
+ final AssignToArgument assignToArgument = annotations.getOnly().prepare(AssignToArgument.class).load();
+ postProcessors.add(createAssignToArgumentPostProcessor(adviceMethod, exit, assignToArgument));
+ } else if (annotation.getAnnotationType().represents(AssignToField.class)) {
+ final AssignToField assignToField = annotations.getOnly().prepare(AssignToField.class).load();
+ postProcessors.add(createAssignToFieldPostProcessor(adviceMethod, exit, assignToField));
+ } else if (annotation.getAnnotationType().represents(AssignToReturn.class)) {
+ final AssignToReturn assignToReturn = annotations.getOnly().prepare(AssignToReturn.class).load();
+ postProcessors.add(createAssignToReturnPostProcessor(adviceMethod, exit, assignToReturn));
+ } else if (annotation.getAnnotationType().represents(AssignTo.class)) {
+ final AssignTo assignTo = annotations.getOnly().prepare(AssignTo.class).load();
+ for (AssignToArgument assignToArgument : assignTo.arguments()) {
+ postProcessors.add(createAssignToArgumentPostProcessor(adviceMethod, exit, assignToArgument));
+ }
+ for (AssignToField assignToField : assignTo.fields()) {
+ postProcessors.add(createAssignToFieldPostProcessor(adviceMethod, exit, assignToField));
+ }
+ for (AssignToReturn assignToReturn : assignTo.returns()) {
+ postProcessors.add(createAssignToReturnPostProcessor(adviceMethod, exit, assignToReturn));
+ }
+ }
+ }
+ return new Compound(postProcessors);
+ }
+
+ private Advice.PostProcessor createAssignToReturnPostProcessor(final MethodDescription.InDefinedShape adviceMethod, final boolean exit, final AssignToReturn assignToReturn) {
+ return new Advice.PostProcessor() {
+ @Override
+ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
+ final TypeDescription.Generic returnType = adviceMethod.getReturnType();
+ if (assignToReturn.index() != -1) {
+ if (!returnType.represents(Object[].class)) {
+ throw new IllegalStateException("Advice method has to return Object[] when setting an index");
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.REFERENCE.loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ IntegerConstant.forValue(assignToReturn.index()),
+ ArrayAccess.REFERENCE.load(),
+ assigner.assign(TypeDescription.Generic.OBJECT, instrumentedMethod.getReturnType(), Assigner.Typing.DYNAMIC),
+ MethodVariableAccess.of(instrumentedMethod.getReturnType()).storeAt(argumentHandler.returned())
+ );
+ } else {
+ final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), instrumentedMethod.getReturnType(), assignToReturn.typing());
+ if (!assign.isValid()) {
+ throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + instrumentedMethod.getReturnType());
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ assign,
+ MethodVariableAccess.of(instrumentedMethod.getReturnType()).storeAt(argumentHandler.returned())
+ );
+ }
+ }
+ };
+ }
+
+ private Advice.PostProcessor createAssignToFieldPostProcessor(final MethodDescription.InDefinedShape adviceMethod, final boolean exit, final AssignToField assignToField) {
+ return new Advice.PostProcessor() {
+ @Override
+ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
+ final FieldDescription field = getFieldLocator(instrumentedType, assignToField).locate(assignToField.value()).getField();
+
+ if (!field.isStatic() && instrumentedMethod.isStatic()) {
+ throw new IllegalStateException("Cannot read non-static field " + field + " from static method " + instrumentedMethod);
+ } else if (instrumentedMethod.isConstructor() && !exit) {
+ throw new IllegalStateException("Cannot access non-static field before calling constructor: " + instrumentedMethod);
+ }
+
+ final TypeDescription.Generic returnType = adviceMethod.getReturnType();
+ if (assignToField.index() != -1) {
+ if (!returnType.represents(Object[].class)) {
+ throw new IllegalStateException("Advice method has to return Object[] when setting an index");
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodVariableAccess.REFERENCE.loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ IntegerConstant.forValue(assignToField.index()),
+ ArrayAccess.REFERENCE.load(),
+ assigner.assign(TypeDescription.Generic.OBJECT, field.getType(), Assigner.Typing.DYNAMIC),
+ FieldAccess.forField(field).write()
+ );
+ } else {
+ final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), field.getType(), assignToField.typing());
+ if (!assign.isValid()) {
+ throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + field.getType());
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.loadThis(),
+ MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ assign,
+ FieldAccess.forField(field).write()
+ );
+ }
+ }
+ };
+ }
+
+ private Advice.PostProcessor createAssignToArgumentPostProcessor(final MethodDescription.InDefinedShape adviceMethod, final boolean exit, final AssignToArgument assignToArgument) {
+ return new Advice.PostProcessor() {
+ @Override
+ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
+ final ParameterDescription param = instrumentedMethod.getParameters().get(assignToArgument.value());
+ final TypeDescription.Generic returnType = adviceMethod.getReturnType();
+ if (assignToArgument.index() != -1) {
+ if (!returnType.represents(Object[].class)) {
+ throw new IllegalStateException("Advice method has to return Object[] when setting an index");
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.REFERENCE.loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ IntegerConstant.forValue(assignToArgument.index()),
+ ArrayAccess.REFERENCE.load(),
+ assigner.assign(TypeDescription.Generic.OBJECT, param.getType(), Assigner.Typing.DYNAMIC),
+ MethodVariableAccess.store(param)
+ );
+ } else {
+ final StackManipulation assign = assigner.assign(returnType, param.getType(), assignToArgument.typing());
+ if (!assign.isValid()) {
+ throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + param.getType());
+ }
+ return new StackManipulation.Compound(
+ MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ assign,
+ MethodVariableAccess.store(param)
+ );
+ }
+ }
+ };
+ }
+
+ public static class Compound implements Advice.PostProcessor {
+
+ /**
+ * The represented post processors.
+ */
+ private final List postProcessors;
+
+ /**
+ * Creates a new compound post processor.
+ *
+ * @param postProcessors The represented post processors.
+ */
+ public Compound(List postProcessors) {
+ this.postProcessors = postProcessors;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public StackManipulation resolve(TypeDescription instrumentedType,
+ MethodDescription instrumentedMethod,
+ Assigner assigner,
+ Advice.ArgumentHandler argumentHandler) {
+ List stackManipulations = new ArrayList(postProcessors.size());
+ for (Advice.PostProcessor postProcessor : postProcessors) {
+ stackManipulations.add(postProcessor.resolve(instrumentedType, instrumentedMethod, assigner, argumentHandler));
+ }
+ return new StackManipulation.Compound(stackManipulations);
+ }
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
index 9114ed4d81..42c26f3e3b 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
@@ -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
@@ -36,4 +36,6 @@
public @interface AssignToReturn {
Assigner.Typing typing() default Assigner.Typing.STATIC;
+
+ int index() default -1;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java
new file mode 100644
index 0000000000..8cff409864
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java
@@ -0,0 +1,22 @@
+package co.elastic.apm.agent.util;
+
+import javax.annotation.Nullable;
+
+public class RemoveOnGetThreadLocal {
+
+ private final ThreadLocal threadLocal = new ThreadLocal<>();
+
+ public void set(@Nullable T value) {
+ threadLocal.set(value);
+ }
+
+ @Nullable
+ public T getAndRemove() {
+ T value = threadLocal.get();
+ if (value != null) {
+ threadLocal.remove();
+ }
+ return value;
+ }
+
+}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index c92a7f32c9..86832d460f 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -25,6 +25,7 @@
package co.elastic.apm.agent.bci;
import co.elastic.apm.agent.MockTracer;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignTo;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
@@ -46,6 +47,7 @@
import org.junit.jupiter.api.Test;
import org.stagemonitor.configuration.ConfigurationRegistry;
+import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -86,6 +88,13 @@ void testFieldAccess() {
assertThat(privateString).isEqualTo("@AssignToField");
}
+ @Test
+ void testFieldAccessArray() {
+ init(configurationRegistry, List.of(new FieldAccessArrayInstrumentation()));
+ assignToField("@AssignToField");
+ assertThat(privateString).isEqualTo("@AssignToField");
+ }
+
public void assignToField(String s) {
}
@@ -99,6 +108,27 @@ public String assignToArgument(String s) {
return s;
}
+ @Test
+ void testAssignToArgumentArray() {
+ init(configurationRegistry, List.of(new AssignToArgumentsInstrumentation()));
+ assertThat(assignToArguments("foo", "bar")).isEqualTo("barfoo");
+ }
+
+ public String assignToArguments(String foo, String bar) {
+ return foo + bar;
+ }
+
+ @Test
+ void testAssignToReturnArray() {
+ init(configurationRegistry, List.of(new AssignToReturnArrayInstrumentation()));
+ assertThat(assignToReturn("foo", "bar")).isEqualTo("foobar");
+ }
+
+ @Nullable
+ public String assignToReturn(String foo, String bar) {
+ return null;
+ }
+
@Test
void testDisabled() {
when(coreConfig.getDisabledInstrumentations()).
@@ -358,6 +388,31 @@ public static String onEnter(@Advice.Argument(0) String s) {
public ElementMatcher super TypeDescription> getTypeMatcher() {
return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
}
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return ElementMatchers.named("assignToField");
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return List.of("test", "experimental");
+ }
+ }
+
+ public static class FieldAccessArrayInstrumentation extends ElasticApmInstrumentation {
+
+ @AssignTo(fields = @AssignToField(index = 0, value = "privateString"))
+ @Advice.OnMethodEnter
+ public static Object[] onEnter(@Advice.Argument(0) String s) {
+ return new Object[]{s};
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ }
+
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
return ElementMatchers.named("assignToField");
@@ -381,6 +436,7 @@ public static String onEnter(@Advice.Argument(0) String s) {
public ElementMatcher super TypeDescription> getTypeMatcher() {
return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
}
+
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
return ElementMatchers.named("assignToArgument");
@@ -391,4 +447,55 @@ public Collection getInstrumentationGroupNames() {
return List.of("test", "experimental");
}
}
+
+ public static class AssignToArgumentsInstrumentation extends ElasticApmInstrumentation {
+
+ @AssignTo(arguments = {
+ @AssignToArgument(index = 0, value = 1),
+ @AssignToArgument(index = 1, value = 0)
+ })
+ @Advice.OnMethodEnter
+ public static Object[] onEnter(@Advice.Argument(0) String foo, @Advice.Argument(1) String bar) {
+ return new Object[]{foo, bar};
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return ElementMatchers.named("assignToArguments");
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return List.of("test", "experimental");
+ }
+ }
+
+ public static class AssignToReturnArrayInstrumentation extends ElasticApmInstrumentation {
+
+ @AssignTo(returns = @AssignToReturn(index = 0))
+ @Advice.OnMethodExit
+ public static Object[] onEnter(@Advice.Argument(0) String foo, @Advice.Argument(1) String bar) {
+ return new Object[]{foo + bar};
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return ElementMatchers.named("assignToReturn");
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return List.of("test", "experimental");
+ }
+ }
}
diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
index 420b6ecaf8..07e0a606bd 100644
--- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
+++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
@@ -26,6 +26,8 @@
import co.elastic.apm.agent.bci.HelperClassManager;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignTo;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.http.client.HttpClientHelper;
import co.elastic.apm.agent.httpclient.helper.ApacheHttpAsyncClientHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
@@ -59,51 +61,6 @@ public class ApacheHttpAsyncClientInstrumentation extends BaseApacheHttpClientIn
@VisibleForAdvice
public static HelperClassManager, HttpContext, HttpRequest>> asyncHelperManager;
- private static class ApacheHttpAsyncClientAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeExecute(@Advice.Argument(value = 0, readOnly = false) HttpAsyncRequestProducer requestProducer,
- @Advice.Argument(2) HttpContext context,
- @Advice.Argument(value = 3, readOnly = false) FutureCallback futureCallback,
- @Advice.Local("span") @Nullable Span span,
- @Advice.Local("wrapped") boolean wrapped) {
- if (tracer == null || tracer.getActive() == null) {
- return;
- }
- final AbstractSpan> parent = tracer.getActive();
- span = parent.createExitSpan();
- if (span != null) {
- span.withType(HttpClientHelper.EXTERNAL_TYPE)
- .withSubtype(HttpClientHelper.HTTP_SUBTYPE)
- .activate();
-
- ApacheHttpAsyncClientHelper, HttpContext, HttpRequest> asyncHelper =
- asyncHelperManager.getForClassLoaderOfClass(HttpAsyncRequestProducer.class);
- TextHeaderSetter headerSetter = headerSetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class);
- if (asyncHelper != null && headerSetter != null) {
- requestProducer = asyncHelper.wrapRequestProducer(requestProducer, span, headerSetter);
- futureCallback = asyncHelper.wrapFutureCallback(futureCallback, context, span);
- wrapped = true;
- }
- }
- }
-
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
- public static void onAfterExecute(@Advice.Local("span") @Nullable Span span,
- @Advice.Local("wrapped") boolean wrapped,
- @Advice.Thrown @Nullable Throwable t) {
- if (span != null) {
- // Deactivate in this thread. Span will be ended and reported by the listener
- span.deactivate();
-
- if (!wrapped) {
- // Listener is not wrapped- we need to end the span so to avoid leak and report error if occurred during method invocation
- span.captureException(t);
- span.end();
- }
- }
- }
- }
-
public ApacheHttpAsyncClientInstrumentation(ElasticApmTracer tracer) {
super(tracer);
asyncHelperManager = HelperClassManager.ForAnyClassLoader.of(tracer,
@@ -144,4 +101,53 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
.and(takesArgument(2, named("org.apache.http.protocol.HttpContext")))
.and(takesArgument(3, named("org.apache.http.concurrent.FutureCallback")));
}
+
+ public static class ApacheHttpAsyncClientAdvice {
+ @AssignTo(arguments = {
+ @AssignToArgument(index = 0, value = 0),
+ @AssignToArgument(index = 1, value = 3)
+ })
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object[] onBeforeExecute(@Advice.Argument(value = 0) HttpAsyncRequestProducer requestProducer,
+ @Advice.Argument(2) HttpContext context,
+ @Advice.Argument(value = 3) FutureCallback> futureCallback) {
+ if (tracer == null || tracer.getActive() == null) {
+ return new Object[]{requestProducer, futureCallback, false, null};
+ }
+ final AbstractSpan> parent = tracer.getActive();
+ Span span = parent.createExitSpan();
+ boolean wrapped = false;
+ if (span != null) {
+ span.withType(HttpClientHelper.EXTERNAL_TYPE)
+ .withSubtype(HttpClientHelper.HTTP_SUBTYPE)
+ .activate();
+
+ ApacheHttpAsyncClientHelper, HttpContext, HttpRequest> asyncHelper =
+ asyncHelperManager.getForClassLoaderOfClass(HttpAsyncRequestProducer.class);
+ TextHeaderSetter headerSetter = headerSetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class);
+ if (asyncHelper != null && headerSetter != null) {
+ requestProducer = asyncHelper.wrapRequestProducer(requestProducer, span, headerSetter);
+ futureCallback = asyncHelper.wrapFutureCallback(futureCallback, context, span);
+ wrapped = true;
+ }
+ }
+ return new Object[]{requestProducer, futureCallback, wrapped, span};
+ }
+
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static void onAfterExecute(@Advice.Enter @Nullable Object[] enter,
+ @Advice.Thrown @Nullable Throwable t) {
+ Span span = enter != null ? (Span) enter[3] : null;
+ if (span != null) {
+ // Deactivate in this thread. Span will be ended and reported by the listener
+ span.deactivate();
+
+ if (!((Boolean) enter[2])) {
+ // Listener is not wrapped- we need to end the span so to avoid leak and report error if occurred during method invocation
+ span.captureException(t);
+ span.end();
+ }
+ }
+ }
+ }
}
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
index ebaddefc6b..20c106a7bb 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
@@ -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
@@ -24,10 +24,12 @@
*/
package co.elastic.apm.agent.es.restclient.v5_6;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentation;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentationHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Span;
+import co.elastic.apm.agent.util.RemoveOnGetThreadLocal;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
@@ -73,40 +75,32 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
}
- private static class ElasticsearchRestClientAsyncAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeExecute(@Advice.Argument(0) String method,
- @Advice.Argument(1) String endpoint,
- @Advice.Argument(3) @Nullable HttpEntity entity,
- @Advice.Argument(value = 5, readOnly = false) ResponseListener responseListener,
- @Advice.Local("span") Span span,
- @Advice.Local("wrapped") boolean wrapped,
- @Advice.Local("helper") ElasticsearchRestClientInstrumentationHelper helper) {
+ public static class ElasticsearchRestClientAsyncAdvice {
+ private static final RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
+ @AssignToArgument(5)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static ResponseListener onBeforeExecute(@Advice.Argument(0) String method,
+ @Advice.Argument(1) String endpoint,
+ @Advice.Argument(3) @Nullable HttpEntity entity,
+ @Advice.Argument(5) ResponseListener responseListener) {
- helper = esClientInstrHelperManager.getForClassLoaderOfClass(Response.class);
+ ElasticsearchRestClientInstrumentationHelper helper = esClientInstrHelperManager.getForClassLoaderOfClass(Response.class);
if (helper != null) {
- span = helper.createClientSpan(method, endpoint, entity);
+ Span span = helper.createClientSpan(method, endpoint, entity);
+ spanTls.set(span);
if (span != null) {
- responseListener = helper.wrapResponseListener(responseListener, span);
- wrapped = true;
+ return helper.wrapResponseListener(responseListener, span);
}
}
+ return responseListener;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
- private static void onAfterExecute(@Advice.Argument(5) ResponseListener responseListener,
- @Advice.Local("span") @Nullable Span span,
- @Advice.Local("wrapped") boolean wrapped,
- @Advice.Local("helper") @Nullable ElasticsearchRestClientInstrumentationHelper helper,
- @Advice.Thrown @Nullable Throwable t) {
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static void onAfterExecute(@Advice.Thrown @Nullable Throwable t) {
+ final Span span = spanTls.getAndRemove();
if (span != null) {
// Deactivate in this thread. Span will be ended and reported by the listener
span.deactivate();
-
- if (!wrapped) {
- // Listener is not wrapped- we need to end the span so to avoid leak and report error if occurred during method invocation
- helper.finishClientSpan(null, span, t);
- }
}
}
}
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java
index f644d57228..231ed3ead2 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java
@@ -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
@@ -34,6 +34,7 @@
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.http.HttpEntity;
import org.elasticsearch.client.Response;
+import org.elasticsearch.client.ResponseListener;
import javax.annotation.Nullable;
@@ -49,24 +50,26 @@ public ElasticsearchClientSyncInstrumentation(ElasticApmTracer tracer) {
super(tracer);
}
- private static class ElasticsearchRestClientAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeExecute(@Advice.Argument(0) String method,
+ public static class ElasticsearchRestClientAdvice {
+ @Nullable
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Span onBeforeExecute(@Advice.Argument(0) String method,
@Advice.Argument(1) String endpoint,
- @Advice.Argument(3) @Nullable HttpEntity entity,
- @Advice.Local("span") Span span,
- @Advice.Local("helper") ElasticsearchRestClientInstrumentationHelper helper) {
- helper = esClientInstrHelperManager.getForClassLoaderOfClass(Response.class);
- if (helper != null) {
- span = helper.createClientSpan(method, endpoint, entity);
+ @Advice.Argument(3) @Nullable HttpEntity entity) {
+ ElasticsearchRestClientInstrumentationHelper helper =
+ esClientInstrHelperManager.getForClassLoaderOfClass(Response.class);
+ if (helper == null) {
+ return null;
}
+ return helper.createClientSpan(method, endpoint, entity);
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Return @Nullable Response response,
- @Advice.Local("span") @Nullable Span span,
- @Advice.Local("helper") @Nullable ElasticsearchRestClientInstrumentationHelper helper,
+ @Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
+ ElasticsearchRestClientInstrumentationHelper helper =
+ esClientInstrHelperManager.getForClassLoaderOfClass(Response.class);
if (helper != null && span != null) {
try {
helper.finishClientSpan(response, span, t);
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
index 6ab505f800..916ef9f1d7 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
@@ -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
@@ -24,10 +24,12 @@
*/
package co.elastic.apm.agent.es.restclient.v6_4;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentation;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentationHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Span;
+import co.elastic.apm.agent.util.RemoveOnGetThreadLocal;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
@@ -67,38 +69,30 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
.and(takesArgument(1, named("org.elasticsearch.client.ResponseListener"))));
}
- private static class ElasticsearchRestClientAsyncAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeExecute(@Advice.Argument(0) Request request,
- @Advice.Argument(value = 1, readOnly = false) ResponseListener responseListener,
- @Advice.Local("span") Span span,
- @Advice.Local("wrapped") boolean wrapped,
- @Advice.Local("helper") ElasticsearchRestClientInstrumentationHelper helper) {
+ public static class ElasticsearchRestClientAsyncAdvice {
+ private static final RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
- helper = esClientInstrHelperManager.getForClassLoaderOfClass(Request.class);
+ @AssignToArgument(1)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static ResponseListener onBeforeExecute(@Advice.Argument(0) Request request,
+ @Advice.Argument(1) ResponseListener responseListener) {
+ ElasticsearchRestClientInstrumentationHelper helper = esClientInstrHelperManager.getForClassLoaderOfClass(Request.class);
if (helper != null) {
- span = helper.createClientSpan(request.getMethod(), request.getEndpoint(), request.getEntity());
+ Span span = helper.createClientSpan(request.getMethod(), request.getEndpoint(), request.getEntity());
if (span != null) {
- responseListener = helper.wrapResponseListener(responseListener, span);
- wrapped = true;
+ spanTls.set(span);
+ return helper.wrapResponseListener(responseListener, span);
}
}
+ return responseListener;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
- private static void onAfterExecute(@Advice.Argument(1) ResponseListener responseListener,
- @Advice.Local("span") @Nullable Span span,
- @Advice.Local("wrapped") boolean wrapped,
- @Advice.Local("helper") @Nullable ElasticsearchRestClientInstrumentationHelper helper,
- @Advice.Thrown @Nullable Throwable t) {
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ public static void onAfterExecute(@Advice.Thrown @Nullable Throwable t) {
+ final Span span = spanTls.getAndRemove();
if (span != null) {
// Deactivate in this thread. Span will be ended and reported by the listener
span.deactivate();
-
- if (!wrapped) {
- // Listener is not wrapped- we need to end the span so to avoid leak and report error if occurred during method invocation
- helper.finishClientSpan(null, span, t);
- }
}
}
}
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java
index c654202f8f..9690336839 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java
@@ -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
@@ -66,23 +66,25 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
.and(takesArgument(0, named("org.elasticsearch.client.Request"))));
}
- private static class ElasticsearchRestClientSyncAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeExecute(@Advice.Argument(0) Request request,
- @Advice.Local("span") Span span,
- @Advice.Local("helper") ElasticsearchRestClientInstrumentationHelper helper) {
+ public static class ElasticsearchRestClientSyncAdvice {
+ @Nullable
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Span onBeforeExecute(@Advice.Argument(0) Request request) {
- helper = esClientInstrHelperManager.getForClassLoaderOfClass(Request.class);
- if (helper != null) {
- span = helper.createClientSpan(request.getMethod(), request.getEndpoint(), request.getEntity());
+ ElasticsearchRestClientInstrumentationHelper helper =
+ esClientInstrHelperManager.getForClassLoaderOfClass(Request.class);
+ if (helper == null) {
+ return null;
}
+ return helper.createClientSpan(request.getMethod(), request.getEndpoint(), request.getEntity());
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Return @Nullable Response response,
- @Advice.Local("span") @Nullable Span span,
- @Advice.Local("helper") @Nullable ElasticsearchRestClientInstrumentationHelper helper,
+ @Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
+ ElasticsearchRestClientInstrumentationHelper helper =
+ esClientInstrHelperManager.getForClassLoaderOfClass(Request.class);
if (helper != null && span != null) {
try {
helper.finishClientSpan(response, span, t);
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java
index c8c7444acb..d71f2d460a 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java
@@ -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
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.kafka;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.kafka.helper.KafkaInstrumentationHelper;
@@ -73,40 +74,36 @@ public Class> getAdviceClass() {
@SuppressWarnings("rawtypes")
public static class KafkaProducerAdvice {
- @SuppressWarnings({"unused", "DuplicatedCode", "ParameterCanBeLocal"})
- @Advice.OnMethodEnter(suppress = Throwable.class)
@Nullable
- public static Span beforeSend(@Advice.Argument(0) final ProducerRecord record,
- @Advice.Argument(value = 1, readOnly = false) @Nullable Callback callback,
- @Advice.Local("helper") @Nullable KafkaInstrumentationHelper helper) {
+ @AssignToArgument(1)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Callback beforeSend(@Advice.Argument(0) final ProducerRecord record,
+ @Advice.Argument(1) @Nullable Callback callback) {
if (tracer == null) {
- return null;
+ return callback;
}
Span span = null;
//noinspection ConstantConditions
- helper = kafkaInstrHelperManager.getForClassLoaderOfClass(KafkaProducer.class);
+ KafkaInstrumentationHelper helper = kafkaInstrHelperManager.getForClassLoaderOfClass(KafkaProducer.class);
if (helper != null) {
span = helper.onSendStart(record);
}
if (span == null) {
- return null;
+ return callback;
}
- //noinspection UnusedAssignment
- callback = helper.wrapCallback(callback, span);
- return span;
+ return helper.wrapCallback(callback, span);
}
- @SuppressWarnings("unused")
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
- public static void afterSend(@Advice.Enter @Nullable final Span span,
- @Advice.Argument(0) final ProducerRecord record,
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ public static void afterSend(@Advice.Argument(0) final ProducerRecord record,
@Advice.This final KafkaProducer thiz,
- @Advice.Local("helper") @Nullable KafkaInstrumentationHelper helper,
@Advice.Thrown final Throwable throwable) {
-
+ final Span span = getActiveExitSpan();
+ //noinspection ConstantConditions
+ KafkaInstrumentationHelper helper = kafkaInstrHelperManager.getForClassLoaderOfClass(KafkaProducer.class);
if (helper != null && span != null) {
helper.onSendEnd(span, record, thiz, throwable);
}
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java
index 1b51d3ff67..f73bf8c311 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerHeadersInstrumentation.java
@@ -83,7 +83,6 @@ public static class KafkaProducerHeadersAdvice {
@Nullable
public static Span beforeSend(@Advice.FieldValue("apiVersions") final ApiVersions apiVersions,
@Advice.Argument(0) final ProducerRecord record,
- @Advice.Local("helper") @Nullable KafkaInstrumentationHelper helper,
@Nullable @Advice.Argument(value = 1, readOnly = false) Callback callback) {
if (tracer == null) {
return null;
@@ -91,7 +90,7 @@ public static Span beforeSend(@Advice.FieldValue("apiVersions") final ApiVersion
Span span = null;
//noinspection ConstantConditions
- helper = kafkaInstrHelperManager.getForClassLoaderOfClass(KafkaProducer.class);
+ KafkaInstrumentationHelper helper = kafkaInstrHelperManager.getForClassLoaderOfClass(KafkaProducer.class);
if (helper != null) {
span = helper.onSendStart(record);
@@ -126,7 +125,6 @@ public static Span beforeSend(@Advice.FieldValue("apiVersions") final ApiVersion
public static boolean afterSend(@Advice.Enter(readOnly = false) @Nullable Span span,
@Advice.Argument(value = 0, readOnly = false) ProducerRecord record,
@Advice.This final KafkaProducer thiz,
- @Advice.Local("helper") @Nullable KafkaInstrumentationHelper helper,
@Advice.Thrown @Nullable final Throwable throwable) {
if (throwable != null && throwable.getMessage().contains("Magic v1 does not support record headers")) {
@@ -149,6 +147,8 @@ record = new ProducerRecord(record.topic(), record.partition(), record.timestamp
return true;
}
}
+ //noinspection ConstantConditions
+ KafkaInstrumentationHelper helper = kafkaInstrHelperManager.getForClassLoaderOfClass(KafkaProducer.class);
if (helper != null && span != null) {
helper.onSendEnd(span, record, thiz, throwable);
}
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
index 6bc245fc3b..a56cbb0470 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
@@ -26,6 +26,9 @@
import co.elastic.apm.agent.bci.HelperClassManager;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignTo;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
import co.elastic.apm.agent.http.client.HttpClientHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
@@ -34,7 +37,6 @@
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
import okhttp3.Call;
import okhttp3.Callback;
@@ -80,25 +82,28 @@ public OkHttp3ClientAsyncInstrumentation(ElasticApmTracer tracer) {
@VisibleForAdvice
public static class OkHttpClient3ExecuteAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
- @Advice.FieldValue(value = "originalRequest", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable okhttp3.Request originalRequest,
- @Advice.Argument(value = 0, readOnly = false) @Nullable Callback callback,
- @Advice.Local("span") Span span) {
+ @AssignTo(
+ fields = @AssignToField(index = 0, value = "originalRequest"),
+ arguments = @AssignToArgument(index = 1, value = 0)
+ )
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
+ @Advice.FieldValue("originalRequest") @Nullable okhttp3.Request originalRequest,
+ @Advice.Argument(0) @Nullable Callback callback) {
if (tracer == null || tracer.getActive() == null || callbackWrapperCreator == null) {
- return;
+ return new Object[] {originalRequest, callback, null};
}
final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz);
if (originalRequest == null || callback == null || wrapperCreator == null) {
- return;
+ return new Object[] {originalRequest, callback, null};
}
final AbstractSpan> parent = tracer.getActive();
okhttp3.Request request = originalRequest;
HttpUrl url = request.url();
- span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.scheme(),
+ Span span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.scheme(),
OkHttpClientHelper.computeHostName(url.host()), url.port());
if (span != null) {
span.activate();
@@ -112,10 +117,12 @@ private static void onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
}
callback = wrapperCreator.wrap(callback, span);
}
+ return new Object[] {originalRequest, callback, span};
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- private static void onAfterEnqueue(@Advice.Local("span") @Nullable Span span) {
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static void onAfterEnqueue(@Advice.Enter @Nullable Object[] enter) {
+ Span span = enter != null ? (Span) enter[2] : null;
if (span != null) {
span.deactivate();
}
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
index dd7a78523f..fc624caec1 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
@@ -25,11 +25,13 @@
package co.elastic.apm.agent.okhttp;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
import co.elastic.apm.agent.http.client.HttpClientHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.impl.transaction.TextHeaderSetter;
+import co.elastic.apm.agent.util.RemoveOnGetThreadLocal;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
@@ -57,52 +59,51 @@ public Class> getAdviceClass() {
@VisibleForAdvice
public static class OkHttpClient3ExecuteAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeExecute( @Advice.FieldValue(value = "originalRequest", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable Object originalRequest,
- @Advice.Local("span") Span span) {
+ private final static RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
- if (tracer == null || tracer.getActive() == null) {
- return;
- }
+ @Nullable
+ @AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object onBeforeExecute(@Advice.FieldValue("originalRequest") @Nullable Object originalRequest) {
- if (originalRequest == null) {
- return;
+ if (tracer == null || tracer.getActive() == null || !(originalRequest instanceof Request)) {
+ return originalRequest;
}
final AbstractSpan> parent = tracer.getActive();
- if (originalRequest instanceof okhttp3.Request) {
- okhttp3.Request request = (okhttp3.Request) originalRequest;
- HttpUrl url = request.url();
- span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.scheme(),
- OkHttpClientHelper.computeHostName(url.host()), url.port());
- if (span != null) {
- span.activate();
- if (headerSetterHelperManager != null) {
- TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class);
- if (headerSetter != null) {
- Request.Builder builder = ((okhttp3.Request) originalRequest).newBuilder();
- span.propagateTraceContext(builder, headerSetter);
- originalRequest = builder.build();
- }
+ okhttp3.Request request = (okhttp3.Request) originalRequest;
+ HttpUrl url = request.url();
+ Span span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.scheme(),
+ OkHttpClientHelper.computeHostName(url.host()), url.port());
+ if (span != null) {
+ spanTls.set(span);
+ span.activate();
+ if (headerSetterHelperManager != null) {
+ TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class);
+ if (headerSetter != null) {
+ Request.Builder builder = ((okhttp3.Request) originalRequest).newBuilder();
+ span.propagateTraceContext(builder, headerSetter);
+ return builder.build();
}
}
}
+ return originalRequest;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Return @Nullable okhttp3.Response response,
@Advice.Thrown @Nullable Throwable t) {
- final AbstractSpan> active = getActive();
- if (active != null && active.isExit()) {
+ final Span span = spanTls.getAndRemove();
+ if (span != null) {
try {
if (response != null) {
int statusCode = response.code();
- ((Span) active).getContext().getHttp().withStatusCode(statusCode);
+ span.getContext().getHttp().withStatusCode(statusCode);
}
- active.captureException(t);
+ span.captureException(t);
} finally {
- active.deactivate().end();
+ span.deactivate().end();
}
}
}
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
index 3c539feca6..710163eb89 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
@@ -26,6 +26,9 @@
import co.elastic.apm.agent.bci.HelperClassManager;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignTo;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
import co.elastic.apm.agent.http.client.HttpClientHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
@@ -38,7 +41,6 @@
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -78,25 +80,28 @@ public OkHttpClientAsyncInstrumentation(ElasticApmTracer tracer) {
@VisibleForAdvice
public static class OkHttpClient3ExecuteAdvice {
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
- @Advice.FieldValue(value = "originalRequest", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable Request originalRequest,
- @Advice.Argument(value = 0, readOnly = false) @Nullable Callback callback,
- @Advice.Local("span") Span span) {
+ @AssignTo(
+ fields = @AssignToField(index = 0, value = "originalRequest"),
+ arguments = @AssignToArgument(index = 1, value = 0)
+ )
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
+ @Advice.FieldValue("originalRequest") @Nullable Request originalRequest,
+ @Advice.Argument(0) @Nullable Callback callback) {
if (tracer == null || tracer.getActive() == null || callbackWrapperCreator == null) {
- return;
+ return new Object[] {originalRequest, callback, null};
}
final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz);
if (originalRequest == null || callback == null || wrapperCreator == null) {
- return;
+ return new Object[] {originalRequest, callback, null};
}
final AbstractSpan> parent = tracer.getActive();
Request request = originalRequest;
URL url = request.url();
- span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.getProtocol(),
+ Span span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.getProtocol(),
OkHttpClientHelper.computeHostName(url.getHost()), url.getPort());
if (span != null) {
span.activate();
@@ -110,10 +115,12 @@ private static void onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
}
callback = wrapperCreator.wrap(callback, span);
}
+ return new Object[] {originalRequest, callback, span};
}
- @Advice.OnMethodExit(suppress = Throwable.class)
- private static void onAfterEnqueue(@Advice.Local("span") @Nullable Span span) {
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static void onAfterEnqueue(@Advice.Enter @Nullable Object[] enter) {
+ Span span = enter != null ? (Span) enter[2] : null;
if (span != null) {
span.deactivate();
}
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
index 2f066d6a07..7097bb7ae6 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
@@ -25,11 +25,13 @@
package co.elastic.apm.agent.okhttp;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
import co.elastic.apm.agent.http.client.HttpClientHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.impl.transaction.TextHeaderSetter;
+import co.elastic.apm.agent.util.RemoveOnGetThreadLocal;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.Request;
import net.bytebuddy.asm.Advice;
@@ -56,43 +58,42 @@ public Class> getAdviceClass() {
@VisibleForAdvice
public static class OkHttpClientExecuteAdvice {
+ private final static RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
- @Advice.OnMethodEnter(suppress = Throwable.class)
- private static void onBeforeExecute(@Advice.FieldValue(value = "originalRequest", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable Object originalRequest,
- @Advice.Local("span") Span span) {
+ @Nullable
+ @AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static Object onBeforeExecute(@Advice.FieldValue("originalRequest") @Nullable Object originalRequest) {
- if (tracer == null || tracer.getActive() == null) {
- return;
+ if (tracer == null || tracer.getActive() == null || !(originalRequest instanceof com.squareup.okhttp.Request)) {
+ return originalRequest;
}
- final AbstractSpan> parent = tracer.getActive();
- if (originalRequest == null) {
- return;
- }
+ final AbstractSpan> parent = tracer.getActive();
- if (originalRequest instanceof com.squareup.okhttp.Request) {
- com.squareup.okhttp.Request request = (com.squareup.okhttp.Request) originalRequest;
- HttpUrl httpUrl = request.httpUrl();
- span = HttpClientHelper.startHttpClientSpan(parent, request.method(), httpUrl.toString(), httpUrl.scheme(),
- OkHttpClientHelper.computeHostName(httpUrl.host()), httpUrl.port());
- if (span != null) {
- span.activate();
- if (headerSetterHelperManager != null) {
- TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class);
- if (headerSetter != null) {
- Request.Builder builder = ((com.squareup.okhttp.Request) originalRequest).newBuilder();
- span.propagateTraceContext(builder, headerSetter);
- originalRequest = builder.build();
- }
+ com.squareup.okhttp.Request request = (com.squareup.okhttp.Request) originalRequest;
+ HttpUrl httpUrl = request.httpUrl();
+ Span span = HttpClientHelper.startHttpClientSpan(parent, request.method(), httpUrl.toString(), httpUrl.scheme(),
+ OkHttpClientHelper.computeHostName(httpUrl.host()), httpUrl.port());
+ if (span != null) {
+ spanTls.set(span);
+ span.activate();
+ if (headerSetterHelperManager != null) {
+ TextHeaderSetter headerSetter = headerSetterHelperManager.getForClassLoaderOfClass(Request.class);
+ if (headerSetter != null) {
+ Request.Builder builder = ((com.squareup.okhttp.Request) originalRequest).newBuilder();
+ span.propagateTraceContext(builder, headerSetter);
+ return builder.build();
}
}
}
+ return originalRequest;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Return @Nullable com.squareup.okhttp.Response response,
- @Advice.Local("span") @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
+ Span span = spanTls.getAndRemove();
if (span != null) {
try {
if (response != null) {
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java
index 5b72b19ee1..2f97fb2b19 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java
@@ -25,6 +25,8 @@
package co.elastic.apm.agent.opentracing.impl;
import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
+import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.transaction.TraceContext;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
@@ -65,17 +67,27 @@ public ToTraceIdInstrumentation() {
super(named("toTraceId"));
}
- @SuppressWarnings("Duplicates")
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void toTraceId(@Advice.FieldValue(value = "textMap", typing = Assigner.Typing.DYNAMIC) @Nullable Iterable> textMap,
- @Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable TraceContext childTraceContext,
- @Advice.Return(readOnly = false) String traceId) {
- if (textMap != null && childTraceContext == null) {
- childTraceContext = parseTextMap(textMap);
+ @Nullable
+ @AssignToField(value = "childTraceContext")
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static TraceContext toTraceId(@Advice.FieldValue(value = "textMap", typing = Assigner.Typing.DYNAMIC) @Nullable Iterable> textMap,
+ @Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
+ if (textMap == null) {
+ return childTraceContext;
}
- if (childTraceContext != null) {
- traceId = childTraceContext.getTraceId().toString();
+ return parseTextMap(textMap);
+
+ }
+
+
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String onExit(@Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
+ if (childTraceContext == null) {
+ return null;
}
+ return childTraceContext.getTraceId().toString();
}
}
@@ -85,17 +97,25 @@ public ToSpanIdInstrumentation() {
super(named("toSpanId"));
}
- @SuppressWarnings("Duplicates")
- @Advice.OnMethodExit(suppress = Throwable.class)
- public static void toSpanId(@Advice.FieldValue(value = "textMap", typing = Assigner.Typing.DYNAMIC) @Nullable Iterable> textMap,
- @Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC, readOnly = false) @Nullable TraceContext childTraceContext,
- @Advice.Return(readOnly = false) String spanId) {
- if (textMap != null && childTraceContext == null) {
- childTraceContext = parseTextMap(textMap);
+ @Nullable
+ @AssignToField(value = "childTraceContext")
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ public static TraceContext toSpanId(@Advice.FieldValue(value = "textMap", typing = Assigner.Typing.DYNAMIC) @Nullable Iterable> textMap,
+ @Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
+ if (textMap == null) {
+ return childTraceContext;
}
- if (childTraceContext != null) {
- spanId = childTraceContext.getParentId().toString();
+ return parseTextMap(textMap);
+ }
+
+ @Nullable
+ @AssignToReturn
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ public static String onExit(@Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
+ if (childTraceContext == null) {
+ return null;
}
+ return childTraceContext.getParentId().toString();
}
}
From 543f3aedc467ab45dcdeaa223e2ff0dc40e6d2e7 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Sat, 6 Jun 2020 15:49:45 +0200
Subject: [PATCH 07/35] Use indy dispatcher for Servlet and JDBC plugins
- Add AssignTo annotation to assign to different targets by returning an Object[]
- Add GlobalState annotation
- Create ThreadLocalRegistry to easily construct global thread locals
- Patch class file version to at least 51 (Java 7) in order to be able to insert
invokedynamic instructions
- Load the whole package of the Advice class from a dedicated plugin classloader
that's a child of the classloader of the instrumented class
- Depend on byte-buddy-dep and manually shade asm so that we can use asm-commons
---
apm-agent-core/pom.xml | 7 +-
.../apm/agent/bci/ElasticApmAgent.java | 105 ++++++++++++++++++
.../agent/bci/ElasticApmInstrumentation.java | 4 +
.../co/elastic/apm/agent/bci/GlobalState.java | 23 ++++
.../apm/agent/bci/HelperClassManager.java | 67 +++++++++++
.../MinimumClassFileVersionValidator.java | 6 +-
.../bci/bytebuddy/postprocessor/AssignTo.java | 24 ++++
.../AssignToFieldPostProcessorFactory.java | 87 ---------------
.../AssignToPostProcessorFactory.java | 87 +++++++++++++--
.../AssignToReturnPostProcessorFactory.java | 61 ----------
.../threadlocal/RemoveOnGetThreadLocal.java | 60 ++++++++++
.../threadlocal/ThreadLocalRegistry.java | 51 +++++++++
.../apm/agent/threadlocal/package-info.java | 28 +++++
.../co/elastic/apm/agent/util/CallDepth.java | 75 +++++++++++++
.../apm/agent/util/PackageScanner.java | 101 +++++++++++++++++
.../agent/util/RemoveOnGetThreadLocal.java | 22 ----
.../apm/agent/bci/InstrumentationTest.java | 8 +-
...asticsearchClientAsyncInstrumentation.java | 2 +-
...asticsearchClientAsyncInstrumentation.java | 2 +-
.../agent/jdbc/ConnectionInstrumentation.java | 19 +---
.../apm/agent/jdbc/JdbcInstrumentation.java | 24 +---
.../agent/jdbc/StatementInstrumentation.java | 96 ++++++----------
.../agent/jdbc/helper/JdbcGlobalState.java | 52 +++++++++
.../apm/agent/jdbc/helper/JdbcHelper.java | 72 ------------
.../apm/agent/jdbc/helper/JdbcHelperImpl.java | 55 +++++----
.../jdbc/AbstractJdbcInstrumentationTest.java | 13 +--
.../okhttp/OkHttp3ClientInstrumentation.java | 2 +-
.../okhttp/OkHttpClientInstrumentation.java | 2 +-
.../AbstractServletInstrumentation.java | 7 +-
.../agent/servlet/AsyncInstrumentation.java | 38 +------
.../servlet/FilterChainInstrumentation.java | 5 -
...RequestStreamRecordingInstrumentation.java | 29 +----
.../apm/agent/servlet/ServletApiAdvice.java | 76 ++++++-------
...elperImpl.java => ServletGlobalState.java} | 25 +++--
.../agent/servlet/ServletInstrumentation.java | 27 -----
.../servlet/ServletTransactionHelper.java | 9 +-
.../ServletVersionInstrumentation.java | 19 ++--
... => ServletTransactionCreationHelper.java} | 8 +-
.../agent/servlet/AbstractServletTest.java | 2 +-
.../apm/agent/servlet/AsyncServletTest.java | 4 +-
...ctExceptionHandlerInstrumentationTest.java | 2 +-
...tractViewRenderingInstrumentationTest.java | 2 +-
elastic-apm-agent/pom.xml | 4 +
.../co/elastic/apm/servlet/WildFlyIT.java | 2 +
pom.xml | 3 +-
45 files changed, 851 insertions(+), 566 deletions(-)
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java
delete mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
delete mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/package-info.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java
delete mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java
create mode 100644 apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java
delete mode 100644 apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelper.java
rename apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/{helper/InputStreamFactoryHelperImpl.java => ServletGlobalState.java} (63%)
rename apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/{ServletTransactionCreationHelperImpl.java => ServletTransactionCreationHelper.java} (91%)
diff --git a/apm-agent-core/pom.xml b/apm-agent-core/pom.xml
index c3a6d1ecaf..436d132ce8 100644
--- a/apm-agent-core/pom.xml
+++ b/apm-agent-core/pom.xml
@@ -62,9 +62,14 @@
net.bytebuddy
- byte-buddy
+ byte-buddy-dep${version.byte-buddy}
+
+ org.ow2.asm
+ asm-tree
+ 8.0.1
+ org.hdrhistogramHdrHistogram
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 817ba58012..196ecf297e 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -41,6 +41,7 @@
import co.elastic.apm.agent.matcher.WildcardMatcher;
import co.elastic.apm.agent.util.DependencyInjectingServiceLoader;
import co.elastic.apm.agent.util.ExecutorUtils;
+import co.elastic.apm.agent.util.PackageScanner;
import co.elastic.apm.agent.util.ThreadUtils;
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
import net.bytebuddy.ByteBuddy;
@@ -48,15 +49,25 @@
import net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.asm.Advice;
+import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
+import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaModule;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.configuration.ConfigurationOption;
@@ -66,6 +77,10 @@
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.security.ProtectionDomain;
import java.util.ArrayList;
@@ -73,6 +88,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -83,9 +99,12 @@
import static co.elastic.apm.agent.bci.ElasticApmInstrumentation.tracer;
import static co.elastic.apm.agent.bci.bytebuddy.ClassLoaderNameMatcher.classLoaderWithName;
import static co.elastic.apm.agent.bci.bytebuddy.ClassLoaderNameMatcher.isReflectionClassLoader;
+import static java.lang.invoke.MethodHandles.dropArguments;
+import static java.lang.invoke.MethodHandles.zero;
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.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameEndsWith;
@@ -93,6 +112,7 @@
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static net.bytebuddy.matcher.ElementMatchers.not;
+import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
public class ElasticApmAgent {
@@ -286,6 +306,47 @@ public boolean matches(TypeDescription typeDescription, ClassLoader classLoader,
}
}
})
+ .transform(new AgentBuilder.Transformer() {
+ @Override
+ public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
+ return builder.visit(new AsmVisitorWrapper.AbstractBase() {
+ @Override
+ public ClassVisitor wrap(TypeDescription typeDescription, ClassVisitor classVisitor, Implementation.Context context,
+ TypePool typePool, FieldList fieldList, MethodList> methodList, int writerFlags, int readerFlags) {
+ return new ClassVisitor(Opcodes.ASM7, classVisitor) {
+ private boolean patchVersion;
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ if (version < Opcodes.V1_7) {
+ patchVersion = true;
+ // up-patching the class file verison to version 7 in order to support advice dispatching via invoke dynamic
+ version = Opcodes.V1_7;
+ }
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (patchVersion) {
+ return new JSRInlinerAdapter(methodVisitor, access, name, descriptor, signature, exceptions);
+ } else {
+ return methodVisitor;
+ }
+ }
+ };
+ }
+
+ @Override
+ public int mergeWriter(int flags) {
+ // class files with version < Java 7 don't require a stack frame map
+ // as we're patching the version to at least 7, we have to compute the frames
+ return flags | COMPUTE_FRAMES;
+ }
+ });
+ }
+ })
.transform(getTransformer(tracer, instrumentation, logger, methodMatcher))
.transform(new AgentBuilder.Transformer() {
@Override
@@ -306,6 +367,13 @@ private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticAp
if (offsetMapping != null) {
withCustomMapping = withCustomMapping.bind(offsetMapping);
}
+ if (instrumentation.indyDispatch()) {
+ try {
+ withCustomMapping = withCustomMapping.bootstrap(ElasticApmAgent.class.getMethod("bootstrap", MethodHandles.Lookup.class, String.class, MethodType.class, String.class, Class.class, MethodHandle.class, String.class, int.class));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
return new AgentBuilder.Transformer.ForAdvice(withCustomMapping)
.advice(new ElementMatcher() {
@Override
@@ -333,6 +401,41 @@ public boolean matches(MethodDescription target) {
.withExceptionHandler(PRINTING);
}
+ private static final ConcurrentMap> classesByPackage = new ConcurrentHashMap<>();
+
+ public static ConstantCallSite bootstrap(MethodHandles.Lookup lookup,
+ String adviceMethodName,
+ MethodType adviceMethodType,
+ String adviceClassName,
+ Class> instrumentedType,
+ MethodHandle instrumentedMethod,
+ String instrumentedMethodName,
+ int enter) {
+ try {
+ Class> adviceClass = Class.forName(adviceClassName);
+ String packageName = adviceClass.getPackage().getName();
+ List helperClasses = classesByPackage.get(packageName);
+ if (helperClasses == null) {
+ classesByPackage.putIfAbsent(packageName, PackageScanner.getClassNames(packageName));
+ helperClasses = classesByPackage.get(packageName);
+ }
+ ClassLoader helperClassLoader = HelperClassManager.ForDispatcher.inject(lookup.lookupClass().getClassLoader(), instrumentedType.getProtectionDomain(), helperClasses, isAnnotatedWith(named(GlobalState.class.getName())));
+ if (helperClassLoader != null) {
+ Class> adviceInHelperCL = helperClassLoader.loadClass(adviceClassName);
+ MethodHandle methodHandle = MethodHandles.lookup().findStatic(adviceInHelperCL, adviceMethodName, adviceMethodType);
+ return new ConstantCallSite(methodHandle);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return new ConstantCallSite(empty(adviceMethodType));
+ }
+
+ public static MethodHandle empty(MethodType type) {
+ Objects.requireNonNull(type);
+ return dropArguments(zero(type.returnType()), 0, type.parameterList());
+ }
+
private static MatcherTimer getOrCreateTimer(Class extends ElasticApmInstrumentation> adviceClass) {
final String name = adviceClass.getName();
MatcherTimer timer = matcherTimers.get(name);
@@ -428,6 +531,8 @@ private static AgentBuilder getAgentBuilder(final ByteBuddy byteBuddy, final Cor
? new SoftlyReferencingTypePoolCache(TypePool.Default.ReaderMode.FAST, 1, isReflectionClassLoader())
: AgentBuilder.PoolStrategy.Default.FAST)
.ignore(any(), isReflectionClassLoader())
+ // 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.")
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
index c4db3392a3..17c6621a74 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
@@ -194,4 +194,8 @@ public Advice.OffsetMapping.Factory> getOffsetMapping() {
public void onTypeMatch(TypeDescription typeDescription, ClassLoader classLoader, ProtectionDomain protectionDomain, @Nullable Class> classBeingRedefined) {
}
+
+ public boolean indyDispatch() {
+ return false;
+ }
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java
new file mode 100644
index 0000000000..8a03e8bd3b
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java
@@ -0,0 +1,23 @@
+package co.elastic.apm.agent.bci;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotating a class with {@link GlobalState} excludes it from being loaded by the plugin class loader.
+ * It will instead be loaded by the agent class loader, which is currently the bootstrap class loader, although that is subject to change.
+ * This will make it's static variables globally available instead of being local to the plugin class loader.
+ *
+ * Normally, all classes within an instrumentation plugin are loaded from a dedicated class loader
+ * that is the child of the class loader that contains the instrumented classes.
+ * If there are multiple class loaders that are instrumented with a given instrumentation plugin,
+ * the instrumentation classes will also be loaded by multiple class loaders.
+ * The effect of that is that state added to static variables in one class loader does not affect the static variable in other class loaders.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface GlobalState {
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
index d06a6ac705..54c7355f78 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
@@ -30,7 +30,10 @@
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
import net.bytebuddy.dynamic.loading.ClassInjector;
+import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
import net.bytebuddy.dynamic.loading.PackageDefinitionStrategy;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.pool.TypePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,8 +43,10 @@
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -271,6 +276,68 @@ private synchronized T loadAndReferenceHelper(Class> classOfTargetClassLoader)
}
}
+ public static class ForDispatcher {
+
+ private static final Map, WeakReference>> alreadyInjected = new WeakHashMap, WeakReference>>();
+
+ /**
+ * Creates an isolated CL that has two parents: the target class loader and the agent CL.
+ * The agent class loader is currently the bootstrap CL but in the future it will be an isolated CL that is a child of the bootstrap CL.
+ */
+ @Nullable
+ public synchronized static ClassLoader inject(@Nullable ClassLoader targetClassLoader, @Nullable ProtectionDomain protectionDomain, List classesToInject, ElementMatcher exclusionMatcher) throws Exception {
+ classesToInject = new ArrayList<>(classesToInject);
+
+ Map, WeakReference> injectedClasses = getOrCreateInjectedClasses(targetClassLoader);
+ if (injectedClasses.containsKey(classesToInject)) {
+ return injectedClasses.get(classesToInject).get();
+ }
+
+ List classesToInjectCopy = new ArrayList<>(classesToInject.size());
+ TypePool pool = new TypePool.Default(TypePool.CacheProvider.NoOp.INSTANCE, ClassFileLocator.ForClassLoader.ofSystemLoader(), TypePool.Default.ReaderMode.FAST);
+ for (Iterator iterator = classesToInject.iterator(); iterator.hasNext(); ) {
+ String className = iterator.next();
+ if (!exclusionMatcher.matches(pool.describe(className).resolve())) {
+ classesToInjectCopy.add(className);
+ }
+ }
+ logger.debug("Creating helper class loader for {} containing {}", targetClassLoader, classesToInjectCopy);
+
+ ClassLoader parent = getHelperClassLoaderParent(targetClassLoader);
+ Map typeDefinitions = getTypeDefinitions(classesToInjectCopy);
+ // child first semantics are important here as the helper CL contains classes that are also present in the agent CL
+ ClassLoader helperCL = new ByteArrayClassLoader.ChildFirst(parent, true, typeDefinitions, ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ injectedClasses.put(classesToInject, new WeakReference<>(helperCL));
+
+
+ return helperCL;
+ }
+
+ private static Map, WeakReference> getOrCreateInjectedClasses(@Nullable ClassLoader targetClassLoader) {
+ Map, WeakReference> injectedClasses = alreadyInjected.get(targetClassLoader);
+ if (injectedClasses == null) {
+ injectedClasses = new HashMap<>();
+ alreadyInjected.put(targetClassLoader, injectedClasses);
+ }
+ return injectedClasses;
+ }
+
+ @Nullable
+ private static ClassLoader getHelperClassLoaderParent(@Nullable ClassLoader targetClassLoader) {
+ ClassLoader agentClassLoader = HelperClassManager.class.getClassLoader();
+ if (agentClassLoader != null && agentClassLoader != ClassLoader.getSystemClassLoader()) {
+ // future world: when the agent is loaded from an isolated class loader
+ // the helper class loader has both, the agent class loader and the target class loader as the parent
+ return new MultipleParentClassLoader(Arrays.asList(agentClassLoader, targetClassLoader));
+ }
+ return targetClassLoader;
+ }
+
+ public synchronized static void clear() {
+ alreadyInjected.clear();
+ }
+ }
+
static Class injectClass(@Nullable ClassLoader targetClassLoader, @Nullable ProtectionDomain pd, String className, boolean isBootstrapClass) throws IOException, ClassNotFoundException {
if (targetClassLoader == null) {
if (isBootstrapClass) {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/MinimumClassFileVersionValidator.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/MinimumClassFileVersionValidator.java
index 3949197d78..76e83380f4 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/MinimumClassFileVersionValidator.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/MinimumClassFileVersionValidator.java
@@ -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
@@ -31,9 +31,9 @@
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
-import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.OpenedClassReader;
+import org.objectweb.asm.ClassVisitor;
public enum MinimumClassFileVersionValidator implements AsmVisitorWrapper {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
index 07c7e78796..1470fbddc2 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
@@ -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.bci.bytebuddy.postprocessor;
import java.lang.annotation.ElementType;
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
deleted file mode 100644
index def042d434..0000000000
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToFieldPostProcessorFactory.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*-
- * #%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.bytebuddy.postprocessor;
-
-import net.bytebuddy.asm.Advice;
-import net.bytebuddy.description.annotation.AnnotationList;
-import net.bytebuddy.description.field.FieldDescription;
-import net.bytebuddy.description.method.MethodDescription;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.TargetType;
-import net.bytebuddy.dynamic.scaffold.FieldLocator;
-import net.bytebuddy.implementation.bytecode.StackManipulation;
-import net.bytebuddy.implementation.bytecode.assign.Assigner;
-import net.bytebuddy.implementation.bytecode.member.FieldAccess;
-import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
-
-import static net.bytebuddy.matcher.ElementMatchers.annotationType;
-
-public class AssignToFieldPostProcessorFactory implements Advice.PostProcessor.Factory {
- @Override
- public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, final boolean exit) {
- final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToField.class));
- if (!annotations.isEmpty()) {
- final AssignToField assignTo = annotations.getOnly().prepare(AssignToField.class).load();
- return new Advice.PostProcessor() {
- @Override
- public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
- final FieldDescription field = getFieldLocator(instrumentedType, assignTo).locate(assignTo.value()).getField();
-
- if (!field.isStatic() && instrumentedMethod.isStatic()) {
- throw new IllegalStateException("Cannot read non-static field " + field + " from static method " + instrumentedMethod);
- } else if (instrumentedMethod.isConstructor() && !exit) {
- throw new IllegalStateException("Cannot access non-static field before calling constructor: " + instrumentedMethod);
- }
-
- final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), field.getType(), assignTo.typing());
- if (!assign.isValid()) {
- throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + field.getType());
- }
- return new StackManipulation.Compound(
- MethodVariableAccess.loadThis(),
- MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
- assign,
- FieldAccess.forField(field).write()
- );
- }
-
- };
- } else {
- return Advice.PostProcessor.NoOp.INSTANCE;
- }
- }
-
- private static FieldLocator getFieldLocator(TypeDescription instrumentedType, AssignToField assignTo) {
- if (assignTo.declaringType() == Void.class) {
- return new FieldLocator.ForClassHierarchy(instrumentedType);
- } else {
- final TypeDescription declaringType = TypeDescription.ForLoadedType.of(assignTo.declaringType());
- if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) {
- throw new IllegalStateException(declaringType + " is no super type of " + instrumentedType);
- }
- return new FieldLocator.ForExactType(declaringType);
- }
- }
-}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java
index 7c25f2f166..c7a599696e 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToPostProcessorFactory.java
@@ -33,12 +33,16 @@
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
+import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
import java.util.ArrayList;
import java.util.List;
@@ -92,7 +96,17 @@ public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMe
}
}
}
- return new Compound(postProcessors);
+ return new Advice.PostProcessor() {
+ @Override
+ public StackManipulation resolve(TypeDescription typeDescription, MethodDescription methodDescription, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
+ final Label jumpToIfNull = new Label();
+ return new StackManipulation.Compound(
+ new AddNullCheck(jumpToIfNull, adviceMethod.getReturnType(), MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter())),
+ new AssignToPostProcessorFactory.Compound(postProcessors).resolve(typeDescription, methodDescription, assigner, argumentHandler),
+ new AddLabel(jumpToIfNull)
+ );
+ }
+ };
}
private Advice.PostProcessor createAssignToReturnPostProcessor(final MethodDescription.InDefinedShape adviceMethod, final boolean exit, final AssignToReturn assignToReturn) {
@@ -143,24 +157,31 @@ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescrip
if (!returnType.represents(Object[].class)) {
throw new IllegalStateException("Advice method has to return Object[] when setting an index");
}
+ final Label jumpToIfNull = new Label();
+ final StackManipulation load = MethodVariableAccess.REFERENCE.loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter());
return new StackManipulation.Compound(
+ new AddNullCheck(jumpToIfNull, TypeDescription.Generic.OBJECT, load),
MethodVariableAccess.loadThis(),
- MethodVariableAccess.REFERENCE.loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
- IntegerConstant.forValue(assignToField.index()),
- ArrayAccess.REFERENCE.load(),
+ load,
+ loadArrayElement(assignToField.index()),
assigner.assign(TypeDescription.Generic.OBJECT, field.getType(), Assigner.Typing.DYNAMIC),
- FieldAccess.forField(field).write()
+ FieldAccess.forField(field).write(),
+ new AddLabel(jumpToIfNull)
);
} else {
final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), field.getType(), assignToField.typing());
if (!assign.isValid()) {
throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + field.getType());
}
+ final Label jumpToIfNull = new Label();
+ final StackManipulation load = MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter());
return new StackManipulation.Compound(
+ new AddNullCheck(jumpToIfNull, TypeDescription.Generic.OBJECT, load),
MethodVariableAccess.loadThis(),
- MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
+ load,
assign,
- FieldAccess.forField(field).write()
+ FieldAccess.forField(field).write(),
+ new AddLabel(jumpToIfNull)
);
}
}
@@ -199,6 +220,12 @@ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescrip
};
}
+ private StackManipulation loadArrayElement(int i) {
+ return new StackManipulation.Compound(
+ IntegerConstant.forValue(i),
+ ArrayAccess.REFERENCE.load());
+ }
+
public static class Compound implements Advice.PostProcessor {
/**
@@ -229,4 +256,50 @@ public StackManipulation resolve(TypeDescription instrumentedType,
return new StackManipulation.Compound(stackManipulations);
}
}
+
+ private static class AddNullCheck implements StackManipulation {
+ private final Label jumpToIfNull;
+ private final TypeDescription.Generic type;
+ private final StackManipulation load;
+
+ public AddNullCheck(Label jumpToIfNull, TypeDescription.Generic type, StackManipulation load) {
+ this.jumpToIfNull = jumpToIfNull;
+ this.type = type;
+ this.load = load;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context context) {
+ Size size = new Size(0, 0);
+ if (!type.isPrimitive()) {
+ size = size.aggregate(load.apply(methodVisitor, context));
+ methodVisitor.visitJumpInsn(Opcodes.IFNULL, jumpToIfNull);
+ }
+ return size;
+ }
+ }
+
+ private static class AddLabel implements StackManipulation {
+ private final Label label;
+
+ public AddLabel(Label label) {
+ this.label = label;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public Size apply(MethodVisitor methodVisitor, Implementation.Context context) {
+ methodVisitor.visitLabel(label);
+ return new Size(0, 0);
+ }
+ }
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
deleted file mode 100644
index 35ccd4af7f..0000000000
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturnPostProcessorFactory.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*-
- * #%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.bytebuddy.postprocessor;
-
-import net.bytebuddy.asm.Advice;
-import net.bytebuddy.description.annotation.AnnotationList;
-import net.bytebuddy.description.method.MethodDescription;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.implementation.bytecode.StackManipulation;
-import net.bytebuddy.implementation.bytecode.assign.Assigner;
-import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
-
-import static net.bytebuddy.matcher.ElementMatchers.annotationType;
-
-public class AssignToReturnPostProcessorFactory implements Advice.PostProcessor.Factory {
- @Override
- public Advice.PostProcessor make(final MethodDescription.InDefinedShape adviceMethod, final boolean exit) {
- final AnnotationList annotations = adviceMethod.getDeclaredAnnotations().filter(annotationType(AssignToReturn.class));
- if (!annotations.isEmpty()) {
- final AssignToReturn assignTo = annotations.getOnly().prepare(AssignToReturn.class).load();
- return new Advice.PostProcessor() {
- @Override
- public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler) {
- final StackManipulation assign = assigner.assign(adviceMethod.getReturnType(), instrumentedMethod.getReturnType(), assignTo.typing());
- if (!assign.isValid()) {
- throw new IllegalStateException("Cannot assign " + adviceMethod.getReturnType() + " to " + instrumentedMethod.getReturnType());
- }
- return new StackManipulation.Compound(
- MethodVariableAccess.of(adviceMethod.getReturnType()).loadFrom(exit ? argumentHandler.exit() : argumentHandler.enter()),
- assign,
- MethodVariableAccess.of(instrumentedMethod.getReturnType()).storeAt(argumentHandler.returned())
- );
- }
- };
- } else {
- return Advice.PostProcessor.NoOp.INSTANCE;
- }
- }
-}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
new file mode 100644
index 0000000000..6b04f7b7c3
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
@@ -0,0 +1,60 @@
+/*-
+ * #%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.threadlocal;
+
+import com.blogspot.mydailyjava.weaklockfree.DetachedThreadLocal;
+
+import javax.annotation.Nullable;
+
+public class RemoveOnGetThreadLocal extends DetachedThreadLocal {
+
+ @Nullable
+ private final T defaultValue;
+
+ public RemoveOnGetThreadLocal() {
+ this(null);
+ }
+
+ public RemoveOnGetThreadLocal(@Nullable T defaultValue) {
+ super(Cleaner.INLINE);
+ this.defaultValue = defaultValue;
+ }
+
+ @Nullable
+ public T getAndRemove() {
+ T value = get();
+ if (value != null) {
+ clear();
+ }
+ return value;
+ }
+
+ @Override
+ @Nullable
+ protected T initialValue(Thread thread) {
+ return defaultValue;
+ }
+
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java
new file mode 100644
index 0000000000..43f174a5c4
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java
@@ -0,0 +1,51 @@
+/*-
+ * #%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.threadlocal;
+
+
+import javax.annotation.Nullable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class ThreadLocalRegistry {
+ private static final ConcurrentMap> registry = new ConcurrentHashMap<>();
+
+ public static RemoveOnGetThreadLocal get(String key) {
+ return get(key, null);
+ }
+
+ public static RemoveOnGetThreadLocal get(Class> adviceClass, String key, @Nullable T defaultValue) {
+ return get(adviceClass.getName() + "." + key);
+ }
+
+ public static RemoveOnGetThreadLocal get(String key, @Nullable T defaultValue) {
+ RemoveOnGetThreadLocal> threadLocal = registry.get(key);
+ if (threadLocal == null) {
+ registry.putIfAbsent(key, new RemoveOnGetThreadLocal(defaultValue));
+ threadLocal = registry.get(key);
+ }
+ return (RemoveOnGetThreadLocal) threadLocal;
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/package-info.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/package-info.java
new file mode 100644
index 0000000000..831582f7c1
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/package-info.java
@@ -0,0 +1,28 @@
+/*-
+ * #%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%
+ */
+@NonnullApi
+package co.elastic.apm.agent.threadlocal;
+
+import co.elastic.apm.agent.annotation.NonnullApi;
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
new file mode 100644
index 0000000000..8240e5f405
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
@@ -0,0 +1,75 @@
+/*-
+ * #%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 java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A utility that makes it easy to detect nested method calls.
+ */
+public class CallDepth {
+ private static final ConcurrentMap registry = new ConcurrentHashMap<>();
+ private final ThreadLocal callDepthPerThread = new ThreadLocal();
+
+ public static CallDepth get(Class> adviceClass) {
+ // we want to return the same CallDepth instance even if the advice class has been loaded from different class loaders
+ String key = adviceClass.getName();
+ CallDepth callDepth = registry.get(key);
+ if (callDepth == null) {
+ registry.putIfAbsent(key, new CallDepth());
+ callDepth = registry.get(key);
+ }
+ return callDepth;
+ }
+
+ /**
+ * Gets and increments the call depth counter.
+ * Returns {@code 0} if this is the outer-most (non-nested) invocation.
+ *
+ * @return the call depth before it has been incremented
+ */
+ public int increment() {
+ AtomicInteger callDepthForCurrentThread = callDepthPerThread.get();
+ if (callDepthForCurrentThread == null) {
+ callDepthForCurrentThread = new AtomicInteger();
+ callDepthPerThread.set(callDepthForCurrentThread);
+ }
+ return callDepthForCurrentThread.getAndIncrement();
+ }
+
+ /**
+ * Decrements and gets the call depth counter.
+ * Returns {@code 0} if this is the outer-most (non-nested) invocation.
+ *
+ * @return the call depth after it has been incremented
+ */
+ public int decrement() {
+ int depth = callDepthPerThread.get().decrementAndGet();
+ assert depth >= 0;
+ return depth;
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java
new file mode 100644
index 0000000000..2752c84155
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java
@@ -0,0 +1,101 @@
+/*-
+ * #%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 java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+public class PackageScanner {
+
+ /**
+ * Returns all class names within a package and sub-packages
+ *
+ * @param basePackage the package to scan
+ * @return all class names within a package and sub-packages
+ * @throws IOException
+ * @throws URISyntaxException
+ */
+ public static List getClassNames(final String basePackage) throws IOException, URISyntaxException {
+ String baseFolderResource = basePackage.replace('.', '/');
+ final List classNames = new ArrayList<>();
+ Enumeration resources = getResourcesFromAgentClassLoader(baseFolderResource);
+ while (resources.hasMoreElements()) {
+ URL resource = resources.nextElement();
+ URI uri = resource.toURI();
+ if (uri.getScheme().equals("jar")) {
+ // avoids FileSystemAlreadyExistsException
+ synchronized (PackageScanner.class) {
+ try (FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
+ final Path basePath = fileSystem.getPath(baseFolderResource).toAbsolutePath();
+ classNames.addAll(listClassNames(basePackage, basePath));
+ }
+ }
+ } else {
+ final Path basePath = Paths.get(uri);
+ if (basePath.toString().contains("test-classes")) {
+ continue;
+ }
+ classNames.addAll(listClassNames(basePackage, basePath));
+ }
+ }
+ return classNames;
+ }
+
+ private static List listClassNames(final String basePackage, final Path basePath) throws IOException {
+ final List classNames = new ArrayList<>();
+ Files.walkFileTree(basePath, new SimpleFileVisitor() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ if (file.toString().endsWith(".class")) {
+ classNames.add(basePackage + "." + basePath.relativize(file).toString().replace('/', '.').replace(".class", ""));
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ return classNames;
+ }
+
+ private static Enumeration getResourcesFromAgentClassLoader(String baseFolderResource) throws IOException {
+ ClassLoader agentCL = PackageScanner.class.getClassLoader();
+ if (agentCL == null) {
+ agentCL = ClassLoader.getSystemClassLoader();
+ }
+ return agentCL.getResources(baseFolderResource);
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java
deleted file mode 100644
index 8cff409864..0000000000
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/RemoveOnGetThreadLocal.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package co.elastic.apm.agent.util;
-
-import javax.annotation.Nullable;
-
-public class RemoveOnGetThreadLocal {
-
- private final ThreadLocal threadLocal = new ThreadLocal<>();
-
- public void set(@Nullable T value) {
- threadLocal.set(value);
- }
-
- @Nullable
- public T getAndRemove() {
- T value = threadLocal.get();
- if (value != null) {
- threadLocal.remove();
- }
- return value;
- }
-
-}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index 86832d460f..dd0f089c0b 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -43,7 +43,6 @@
import org.apache.commons.math.util.MathUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.stagemonitor.configuration.ConfigurationRegistry;
@@ -236,7 +235,6 @@ void testDontInstrumentOldClassFileVersions() {
}
@Test
- @Disabled("this is currently a limitation in Byte Buddy")
void testSuppressException() {
ElasticApmAgent.initInstrumentation(tracer,
ByteBuddyAgent.install(),
@@ -282,7 +280,7 @@ private String interceptMe() {
public static class TestInstrumentation extends ElasticApmInstrumentation {
@AssignToReturn
- @Advice.OnMethodExit(inline = false)
+ @Advice.OnMethodExit
public static String onMethodExit() {
return "intercepted";
}
@@ -454,7 +452,7 @@ public static class AssignToArgumentsInstrumentation extends ElasticApmInstrumen
@AssignToArgument(index = 0, value = 1),
@AssignToArgument(index = 1, value = 0)
})
- @Advice.OnMethodEnter
+ @Advice.OnMethodEnter(inline = false)
public static Object[] onEnter(@Advice.Argument(0) String foo, @Advice.Argument(1) String bar) {
return new Object[]{foo, bar};
}
@@ -478,7 +476,7 @@ public Collection getInstrumentationGroupNames() {
public static class AssignToReturnArrayInstrumentation extends ElasticApmInstrumentation {
@AssignTo(returns = @AssignToReturn(index = 0))
- @Advice.OnMethodExit
+ @Advice.OnMethodExit(inline = false)
public static Object[] onEnter(@Advice.Argument(0) String foo, @Advice.Argument(1) String bar) {
return new Object[]{foo + bar};
}
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
index 20c106a7bb..630e5ddfb9 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
@@ -29,7 +29,7 @@
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentationHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Span;
-import co.elastic.apm.agent.util.RemoveOnGetThreadLocal;
+import co.elastic.apm.agent.threadlocal.RemoveOnGetThreadLocal;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
index 916ef9f1d7..83fa988279 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
@@ -29,7 +29,7 @@
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentationHelper;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Span;
-import co.elastic.apm.agent.util.RemoveOnGetThreadLocal;
+import co.elastic.apm.agent.threadlocal.RemoveOnGetThreadLocal;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java
index 244d0b71dc..44d1604493 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java
@@ -25,8 +25,6 @@
package co.elastic.apm.agent.jdbc;
import co.elastic.apm.agent.bci.VisibleForAdvice;
-import co.elastic.apm.agent.impl.ElasticApmTracer;
-import co.elastic.apm.agent.jdbc.helper.JdbcHelper;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
@@ -52,24 +50,11 @@
*/
public class ConnectionInstrumentation extends JdbcInstrumentation {
- public ConnectionInstrumentation(ElasticApmTracer tracer) {
- super(tracer);
- }
-
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static void storeSql(@Advice.Return PreparedStatement statement,
@Advice.Argument(0) String sql) {
-
- if (jdbcHelperManager == null) {
- return;
- }
-
- JdbcHelper jdbcHelper = jdbcHelperManager.getForClassLoaderOfClass(PreparedStatement.class);
- if (jdbcHelper != null) {
- jdbcHelper.mapStatementToSql(statement, sql);
- }
-
+ jdbcHelper.mapStatementToSql(statement, sql);
}
@Override
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
index 87ad638219..d79fa30bdc 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
@@ -25,12 +25,8 @@
package co.elastic.apm.agent.jdbc;
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
-import co.elastic.apm.agent.bci.HelperClassManager;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
-import co.elastic.apm.agent.impl.ElasticApmTracer;
-import co.elastic.apm.agent.jdbc.helper.JdbcHelper;
+import co.elastic.apm.agent.jdbc.helper.JdbcHelperImpl;
-import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
@@ -38,23 +34,15 @@ public abstract class JdbcInstrumentation extends ElasticApmInstrumentation {
private static final Collection JDBC_GROUPS = Collections.singleton("jdbc");
- @VisibleForAdvice
- @Nullable
- public static HelperClassManager jdbcHelperManager = null;
-
- public JdbcInstrumentation(ElasticApmTracer tracer) {
- synchronized (JdbcInstrumentation.class) {
- if (jdbcHelperManager == null) {
- jdbcHelperManager = HelperClassManager.ForSingleClassLoader.of(tracer,
- "co.elastic.apm.agent.jdbc.helper.JdbcHelperImpl",
- "co.elastic.apm.agent.jdbc.helper.JdbcHelperImpl$1");
- }
- }
- }
+ protected static JdbcHelperImpl jdbcHelper = new JdbcHelperImpl();
@Override
public final Collection getInstrumentationGroupNames() {
return JDBC_GROUPS;
}
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
}
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java
index d1ffb7c95b..4b5e7c7678 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java
@@ -27,7 +27,6 @@
import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Span;
-import co.elastic.apm.agent.jdbc.helper.JdbcHelper;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
@@ -55,8 +54,7 @@ public abstract class StatementInstrumentation extends JdbcInstrumentation {
private final ElementMatcher super MethodDescription> methodMatcher;
- StatementInstrumentation(ElasticApmTracer tracer, ElementMatcher super MethodDescription> methodMatcher) {
- super(tracer);
+ StatementInstrumentation(ElementMatcher super MethodDescription> methodMatcher) {
this.methodMatcher = methodMatcher;
}
@@ -91,7 +89,7 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
public static class ExecuteWithQueryInstrumentation extends StatementInstrumentation {
public ExecuteWithQueryInstrumentation(ElasticApmTracer tracer) {
- super(tracer,
+ super(
named("execute").or(named("executeQuery"))
.and(takesArgument(0, String.class))
.and(isPublic())
@@ -100,25 +98,20 @@ public ExecuteWithQueryInstrumentation(ElasticApmTracer tracer) {
@Nullable
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Span onBeforeExecute(@Advice.This Statement statement,
@Advice.Argument(0) String sql) {
- if (tracer == null || jdbcHelperManager == null) {
+ if (tracer == null) {
return null;
}
- JdbcHelper helperImpl = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
- if (helperImpl == null) {
- return null;
- }
-
- return helperImpl.createJdbcSpan(sql, statement, tracer.getActive(), false);
+ return jdbcHelper.createJdbcSpan(sql, statement, tracer.getActive(), false);
}
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.This Statement statement,
@Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
@@ -149,7 +142,7 @@ public static void onAfterExecute(@Advice.This Statement statement,
public static class ExecuteUpdateWithQueryInstrumentation extends StatementInstrumentation {
public ExecuteUpdateWithQueryInstrumentation(ElasticApmTracer tracer) {
- super(tracer,
+ super(
named("executeUpdate").or(named("executeLargeUpdate"))
.and(takesArgument(0, String.class))
.and(isPublic())
@@ -158,23 +151,18 @@ public ExecuteUpdateWithQueryInstrumentation(ElasticApmTracer tracer) {
@Nullable
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Span onBeforeExecute(@Advice.This Statement statement,
@Advice.Argument(0) String sql) {
- if (tracer == null || jdbcHelperManager == null) {
+ if (tracer == null) {
return null;
}
- JdbcHelper helperImpl = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
- if (helperImpl == null) {
- return null;
- }
-
- return helperImpl.createJdbcSpan(sql, statement, tracer.getActive(), false);
+ return jdbcHelper.createJdbcSpan(sql, statement, tracer.getActive(), false);
}
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t,
@Advice.Return long returnValue /* bytebuddy converts int to long for us here ! */) {
@@ -200,23 +188,16 @@ public static void onAfterExecute(@Advice.Enter @Nullable Span span,
public static class AddBatchInstrumentation extends StatementInstrumentation {
public AddBatchInstrumentation(ElasticApmTracer tracer) {
- super(tracer,
+ super(
nameStartsWith("addBatch")
.and(takesArgument(0, String.class))
.and(isPublic())
);
}
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void storeSql(@Advice.This Statement statement, @Advice.Argument(0) String sql) {
- if (jdbcHelperManager == null) {
- return;
- }
-
- JdbcHelper helperImpl = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
- if (helperImpl != null) {
- helperImpl.mapStatementToSql(statement, sql);
- }
+ jdbcHelper.mapStatementToSql(statement, sql);
}
}
@@ -231,7 +212,7 @@ public static void storeSql(@Advice.This Statement statement, @Advice.Argument(0
*/
public static class ExecuteBatchInstrumentation extends StatementInstrumentation {
public ExecuteBatchInstrumentation(ElasticApmTracer tracer) {
- super(tracer,
+ super(
named("executeBatch").or(named("executeLargeBatch"))
.and(takesArguments(0))
.and(isPublic())
@@ -240,23 +221,18 @@ public ExecuteBatchInstrumentation(ElasticApmTracer tracer) {
}
@Nullable
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
@SuppressWarnings("DuplicatedCode")
public static Span onBeforeExecute(@Advice.This Statement statement) {
- if (tracer == null || jdbcHelperManager == null) {
- return null;
- }
- JdbcHelper helper = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
- if (helper == null) {
+ if (tracer == null) {
return null;
}
-
- String sql = helper.retrieveSqlForStatement(statement);
- return helper.createJdbcSpan(sql, statement, tracer.getActive(), true);
+ String sql = jdbcHelper.retrieveSqlForStatement(statement);
+ return jdbcHelper.createJdbcSpan(sql, statement, tracer.getActive(), true);
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Enter @Nullable Span span,
@Advice.Thrown Throwable t,
@Advice.Return Object returnValue) {
@@ -300,7 +276,7 @@ public static void onAfterExecute(@Advice.Enter @Nullable Span span,
*/
public static class ExecuteUpdateNoQueryInstrumentation extends StatementInstrumentation {
public ExecuteUpdateNoQueryInstrumentation(ElasticApmTracer tracer) {
- super(tracer,
+ super(
named("executeUpdate").or(named("executeLargeUpdate"))
.and(takesArguments(0))
.and(isPublic())
@@ -308,23 +284,18 @@ public ExecuteUpdateNoQueryInstrumentation(ElasticApmTracer tracer) {
}
@Nullable
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
@SuppressWarnings("DuplicatedCode")
public static Span onBeforeExecute(@Advice.This Statement statement) {
- if (tracer == null || jdbcHelperManager == null) {
+ if (tracer == null) {
return null;
}
- JdbcHelper helperImpl = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
- if (helperImpl == null) {
- return null;
- }
-
- String sql = helperImpl.retrieveSqlForStatement(statement);
- return helperImpl.createJdbcSpan(sql, statement, tracer.getActive(), true);
+ String sql = jdbcHelper.retrieveSqlForStatement(statement);
+ return jdbcHelper.createJdbcSpan(sql, statement, tracer.getActive(), true);
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t,
@Advice.Return long returnValue /* bytebuddy converts int to long for us here ! */) {
@@ -353,7 +324,7 @@ public static void onAfterExecute(@Advice.Enter @Nullable Span span,
*/
public static class ExecutePreparedStatementInstrumentation extends StatementInstrumentation {
public ExecutePreparedStatementInstrumentation(ElasticApmTracer tracer) {
- super(tracer,
+ super(
named("execute").or(named("executeQuery"))
.and(takesArguments(0))
.and(isPublic())
@@ -361,20 +332,17 @@ public ExecutePreparedStatementInstrumentation(ElasticApmTracer tracer) {
}
@Nullable
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
@SuppressWarnings("DuplicatedCode")
public static Span onBeforeExecute(@Advice.This Statement statement) {
- if (tracer != null && jdbcHelperManager != null) {
- JdbcHelper helperImpl = jdbcHelperManager.getForClassLoaderOfClass(Statement.class);
- if (helperImpl != null) {
- @Nullable String sql = helperImpl.retrieveSqlForStatement(statement);
- return helperImpl.createJdbcSpan(sql, statement, tracer.getActive(), true);
- }
+ if (tracer != null) {
+ @Nullable String sql = jdbcHelper.retrieveSqlForStatement(statement);
+ return jdbcHelper.createJdbcSpan(sql, statement, tracer.getActive(), true);
}
return null;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.This Statement statement,
@Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java
new file mode 100644
index 0000000000..92993a2ec9
--- /dev/null
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java
@@ -0,0 +1,52 @@
+/*-
+ * #%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.jdbc.helper;
+
+import co.elastic.apm.agent.bci.GlobalState;
+import co.elastic.apm.agent.util.DataStructures;
+import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
+
+import java.sql.Connection;
+
+@GlobalState
+public class JdbcGlobalState {
+
+ // Important implementation note:
+ //
+ // because this class is potentially loaded from multiple classloaders, making those fields 'static' will not
+ // have the expected behavior, thus, any direct reference to `JdbcHelperImpl` should only be obtained from the
+ // HelperClassManager instance.
+ public static final WeakConcurrentMap${project.groupId}
From 75b5a29922e48221739393983d067a616301c237 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Mon, 8 Jun 2020 08:42:20 +0200
Subject: [PATCH 10/35] Fix IndyBootstrapDispatcher noop MethodHandle
---
.../elastic/apm/agent/bci/IndyBootstrap.java | 13 +++------
.../resources/bootstrap/IndyBootstrap.clazz | Bin 2712 -> 0 bytes
.../bootstrap/IndyBootstrapDispatcher.clazz | Bin 0 -> 3401 bytes
...trap.java => IndyBootstrapDispatcher.java} | 25 +++++++++++++++---
4 files changed, 26 insertions(+), 12 deletions(-)
delete mode 100644 apm-agent-core/src/main/resources/bootstrap/IndyBootstrap.clazz
create mode 100644 apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.clazz
rename apm-agent-core/src/main/resources/bootstrap/{IndyBootstrap.java => IndyBootstrapDispatcher.java} (64%)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
index 012bf9005d..05bd304e4f 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
@@ -46,18 +46,13 @@
/**
* When {@link ElasticApmInstrumentation#indyDispatch()} returns {@code true},
* we instruct byte buddy to dispatch {@linkplain Advice.OnMethodEnter#inline()} non-inlined advices} via an invokedynamic (indy) instruction.
- *
- *
- *
- *
- * @see ElasticApmInstrumentation#indyDispatch()
*/
public class IndyBootstrap {
- private static final String INDY_BOOTSTRAP_CLASS_NAME = "java.lang.IndyBootstrap";
- private static final String INDY_BOOTSTRAP_RESOURCE = "bootstrap/IndyBootstrap.clazz";
+ private static final String INDY_BOOTSTRAP_CLASS_NAME = "java.lang.IndyBootstrapDispatcher";
+ private static final String INDY_BOOTSTRAP_RESOURCE = "bootstrap/IndyBootstrapDispatcher.clazz";
private static final ConcurrentMap> classesByPackage = new ConcurrentHashMap<>();
@Nullable
- private static Method indyBootstrapMethod;
+ static Method indyBootstrapMethod;
public static Method getIndyBootstrapMethod() {
if (indyBootstrapMethod != null) {
@@ -87,7 +82,7 @@ private static Class> initIndyBootstrap() throws Exception {
}
/**
- * Is called by {@code java.lang.IndyBootstrap#bootstrap} via reflection.
+ * Is called by {@code java.lang.IndyBootstrapDispatcher#bootstrap} via reflection.
*
*
* This is to make it impossible for OSGi or other filtering class loaders to restrict access to classes in the bootstrap class loader.
diff --git a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrap.clazz b/apm-agent-core/src/main/resources/bootstrap/IndyBootstrap.clazz
deleted file mode 100644
index d6679e595ac94d012d1618718ea28a25af704bdd..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 2712
zcmc&$TXz#x6#fpmoMbxCQlJp-AkYh!3MiKpQ7Du~TM(vJBHpISGz^{2WKAYj-dqd+
zi);BLK0uc)T|TKx|C9QinWS@40{Z4d&YanMe|zuqoxOMFkH3ES8Nhi21~Pc3i0!y0
zaoxZ)ZWy?U8HrmGvj!w81}@`W1Mgu@E%OF$X3Z!@2%iu;O&`nzjA9=d_L`z$wklibB_K=#n1b!sDi+D_=B^qw6L;_$08d?s%w-
zYe{q<)3)bXZsd?fL56l|7B|1FInBroeCmwQiQ1vBQWMy&86~L*jHCuPoMpT1MT@rA
zjyX>+?#|L9-W%Oz{U9Khhcrk1jzHSI4(+3_Hr=3EV92hoxiu##j@$O?rW`t4Y_dxZ
zcHm_};wxcAC9dN$C!U8~zZHe;RmYE8)d4N~e~0YOpxVD-%~q_f{*iYb*11Gd7c0l=x8MzC@KxX1NXD=H-&7KmWI4
zR(Wc<_bUcX{om@@u!442b7tJQ^?Q2yjH*c!JF&~eFm|vQHW#VHM>};b2*!Vd5cN6OXWB!o#Y-krdu_9k8x!Kk8n(
z%GDRxof`I>N8os>=|f((J$eqBu*h0_2{b9^!BiVpl)!;h(kB$hL#nT^xErdx<(irX
zcb3@+V?AT2W5FWblfrrjJf}bsN8qwvpq***Kr4)nNS|{XGiR{LsT;+eun^icmivw|
z-LN~!G>*G@Tnj_{vA}@ud{K$-MWIjVxg_A8lWhdfG@M9aR5$PF`r98&iRi;G|8okp
zPIU;}(7h%}yqb2Y_
zI?N0;Ti3CzJn$n1tEGYG7%CI;9g{5b6KaN^O-S#_$y;;$we}t+dGzb(r}3)EptQNW1-bjW~nW1H_)-N(zV`
z<5TU%=|OS&1SfHdRH@x*%G5nP!5MnV#=A*kbMfwVYV+~#EY9%+8D?=FZ=irTDIKAY
zB>n})PkdaUkBi)oBJ($oQSC3Ze-2k@yajLZcQxLTUq!Bt#S@ETqh%Nv5_x}e>Vp(%)C
zO8m&k$VPEa9O??%F)d?8K?1W9VpEpoEI787m(V(VQ(M&fa+*2QXY12BJ>&F^=+3N_
z9U$Z6SaRt4=-Ak}gq;CK!(6oHbf53MW@d9bxg*C7({N5m2z7Q%N(c{HSzSWiuwm+>
zh50Gnp3tUpq{N4Hr!JM|Rpi{x`odFFM{9M@U2@V9(cdZ9m%H;h7
ztv8tL3ebF!K`oa{8IH)O#9TE6IL_S8=y}JmOi>xrvht(WDSJkOV(U)9HierKHU|Ve
zw^#|m%#`K)lywAnDtNk=49k#%(E&fW5(224Cd+2{|$)3&w%e0DjYoaYxoQ-
zMi(t3JL7G!EKl`?goe`DT1TjGg!%(0$XHZy3%x3~V~2{{Sd#IUinsBOjCWPMhxb){
zfDcuCgpXBxf=?y1l@u0@Swx~{Iz^ZW?>|#<5TBQEZxFDGFYu*`ukba;Yf1Ozg6SCZ
zdcfzMxFca-iK9ZLioIBtHLqj@_6DL}@6?q}UHD4aUZPYm#o19hPqLF{L@&vUTCSjv
zO|$Jf%P`_|up_pV#L90FjvUX9gk!;O@Txv=EdWxV6dZ^?iIXDF@$9&5H(_fTLam`Q
z$XFb6n@WC660Mwj6Y23Y5^9WvaodYAKm4rsEQNiP>A9l_F65|FU_Svtk@p{wYf`!uPb
zsMs2GT47Xtv3R&Y7}V~SYeSd(yGWTf%q#)s7#RuLdig-P{c#zfl)2_A+|bA{`-4aisfv!ok@s$_iIZ*})0NDsKF!fvh+
zmso>6+^Ze~_F^A@33XlMqHvYqQ%PHv`lAm~^()SCmFlBEB9snwr_$m5sdRNuDjhkH
zO3R6GDjiK!r_%M2M1-r{B(EZzT0!+Mi1bJAL+OttVk?LyqQ7CHX9Y3w`yrHN)I3B@
zzj7ZP;#~a}n-Vc;6}5uYm>^Sl6`MkuvB}C!+Rc!9Bd5m~EQS%55{nYv$eH#WhbdQhdlYw9aFMr5FHi5F#Xr4^8tUJ{m6_z_aEM>A
zJ4{QWG|RO1f=D)ZM@SQ8U8b+Sj6&>=QnuRN9ph=l-SyKWnVk1<94{e?6Qnj%hiCsJ
z?Rz>-Qpd~OA3@|Fh8&a8F5{Gp)9yMbV~86`7I=^$N1mWn#$fyj>Ur`PHJ+htl@P=^
z=Fx*xf;92x!iinVtp}%*bmaURTi!(?(6)-kcvHNYm0m^53buTYn)|4|_!x;aLD~BA
z12+o|v?d}F>YCkn<7gnro4K~2k!qXQ(Aq+OL=E7^CZlSS&d2CzlB6nJKqtQn)wc5=
IoDL-Z1uw#r;s5{u
literal 0
HcmV?d00001
diff --git a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrap.java b/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.java
similarity index 64%
rename from apm-agent-core/src/main/resources/bootstrap/IndyBootstrap.java
rename to apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.java
index fb6f1a02ee..aed1a54a91 100644
--- a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrap.java
+++ b/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.java
@@ -11,6 +11,16 @@
public class IndyBootstrap {
public static Method bootstrap;
+ private static final MethodHandle VOID_NOOP;
+
+ static {
+ try {
+ VOID_NOOP = MethodHandles.lookup().findStatic(IndyBootstrap.class, "voidNoop", MethodType.methodType(void.class));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
public static CallSite bootstrap(MethodHandles.Lookup lookup,
String adviceMethodName,
MethodType adviceMethodType,
@@ -35,11 +45,20 @@ public static CallSite bootstrap(MethodHandles.Lookup lookup,
}
if (callSite == null) {
Class> returnType = adviceMethodType.returnType();
- Object returnTypeDefaultValue;
- returnTypeDefaultValue = Array.get(Array.newInstance(returnType, 1), 0);
- MethodHandle noop = MethodHandles.dropArguments(MethodHandles.constant(returnType, returnTypeDefaultValue), 0, adviceMethodType.parameterList());
+ MethodHandle noopNoArg;
+ if (returnType == void.class) {
+ noopNoArg = VOID_NOOP;
+ } else if (!returnType.isPrimitive()) {
+ noopNoArg = MethodHandles.constant(returnType, null);
+ } else {
+ noopNoArg = MethodHandles.constant(returnType, Array.get(Array.newInstance(returnType, 1), 0));
+ }
+ MethodHandle noop = MethodHandles.dropArguments(noopNoArg, 0, adviceMethodType.parameterList());
callSite = new ConstantCallSite(noop);
}
return callSite;
}
+
+ public static void voidNoop() {
+ }
}
From 4b3aa01dcea64f6fade764dc588e60d9849518bb Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 12 Jun 2020 08:57:59 +0200
Subject: [PATCH 11/35] Polish and documentation
- Rename helper class loader to plugin class loader
- Add GlobalVariables registry
---
.../agent/bci/ElasticApmInstrumentation.java | 40 ++++++
.../co/elastic/apm/agent/bci/GlobalState.java | 7 +-
.../apm/agent/bci/HelperClassManager.java | 22 +--
.../elastic/apm/agent/bci/IndyBootstrap.java | 135 ++++++++++++++++--
.../threadlocal/RemoveOnGetThreadLocal.java | 30 +++-
.../threadlocal/ThreadLocalRegistry.java | 51 -------
.../co/elastic/apm/agent/util/CallDepth.java | 33 +++++
.../apm/agent/util/GlobalVariables.java | 65 +++++++++
...asticsearchClientAsyncInstrumentation.java | 2 +-
...asticsearchClientAsyncInstrumentation.java | 2 +-
.../agent/jdbc/ConnectionInstrumentation.java | 2 -
.../apm/agent/jdbc/JdbcInstrumentation.java | 4 +-
.../agent/jdbc/StatementInstrumentation.java | 5 -
.../agent/jdbc/helper/JdbcGlobalState.java | 5 -
.../{JdbcHelperImpl.java => JdbcHelper.java} | 4 +-
.../jdbc/AbstractJdbcInstrumentationTest.java | 4 +-
.../okhttp/OkHttp3ClientInstrumentation.java | 2 +-
.../okhttp/OkHttpClientInstrumentation.java | 2 +-
.../agent/servlet/AsyncInstrumentation.java | 16 +--
...RequestStreamRecordingInstrumentation.java | 41 ++----
.../apm/agent/servlet/ServletApiAdvice.java | 9 +-
.../servlet/ServletTransactionHelper.java | 15 --
.../ServletVersionInstrumentation.java | 80 ++++-------
.../RecordingServletInputStreamWrapper.java | 7 +-
.../ServletTransactionCreationHelper.java | 4 -
.../agent/servlet/helper/package-info.java | 44 +-----
26 files changed, 361 insertions(+), 270 deletions(-)
delete mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/util/GlobalVariables.java
rename apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/{JdbcHelperImpl.java => JdbcHelper.java} (99%)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
index 17c6621a74..e657bc482c 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
@@ -195,6 +195,46 @@ public Advice.OffsetMapping.Factory> getOffsetMapping() {
public void onTypeMatch(TypeDescription typeDescription, ClassLoader classLoader, ProtectionDomain protectionDomain, @Nullable Class> classBeingRedefined) {
}
+ /**
+ * When this method returns {@code true} the whole package (starting at the {@linkplain #getAdviceClass() advice's} package)
+ * will be loaded from a plugin class loader that has both the agent class loader and the class loader of the class this instruments as
+ * the parent.
+ *
+ * This instructs Byte Buddy to dispatch to the advice methods via an {@code INVOKEDYNAMIC} instruction.
+ * Upon first invocation of an instrumented method,
+ * this will call {@link IndyBootstrap#bootstrap} to determine the target {@link java.lang.invoke.ConstantCallSite}.
+ *
+ *
+ * Things to watch out for when using indy dispatch:
+ *
+ *
+ * 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.
+ *
+ *
+ * Don't use {@link ThreadLocal}s as it can lead to class loader leaks.
+ * Use {@link co.elastic.apm.agent.threadlocal.RemoveOnGetThreadLocal} instead.
+ *
+ *
+ * Due to the automatic plugin classloader creation that is based on package scanning,
+ * plugins need be in their own uniquely named package.
+ * As the package of the {@link #getAdviceClass()} is used as the root,
+ * all advices have to be at the top level of the plugin.
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ *
+ * @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.
+ * @see IndyBootstrap
+ */
public boolean indyDispatch() {
return false;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java
index 1527ec944e..0b83f22c4f 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/GlobalState.java
@@ -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
@@ -40,6 +40,9 @@
* the instrumentation classes will also be loaded by multiple class loaders.
* The effect of that is that state added to static variables in one class loader does not affect the static variable in other class loaders.
*
+ *
+ * An alternative to this is {@link co.elastic.apm.agent.util.GlobalVariables} which can be used to make individual variables scoped globally.
+ *
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
index 72fd9afcab..ddb8575b4a 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
@@ -276,7 +276,7 @@ private synchronized T loadAndReferenceHelper(Class> classOfTargetClassLoader)
}
}
- public static class ForDispatcher {
+ public static class ForIndyPlugin {
private static final Map, WeakReference>> alreadyInjected = new WeakHashMap, WeakReference>>();
@@ -285,7 +285,7 @@ public static class ForDispatcher {
* The agent class loader is currently the bootstrap CL but in the future it will be an isolated CL that is a child of the bootstrap CL.
*/
@Nullable
- public synchronized static ClassLoader inject(@Nullable ClassLoader targetClassLoader, @Nullable ProtectionDomain protectionDomain, List classesToInject, ElementMatcher super TypeDescription> exclusionMatcher) throws Exception {
+ public synchronized static ClassLoader getOrCreatePluginClassLoader(@Nullable ClassLoader targetClassLoader, @Nullable ProtectionDomain protectionDomain, List classesToInject, ElementMatcher super TypeDescription> exclusionMatcher) throws Exception {
classesToInject = new ArrayList<>(classesToInject);
Map, WeakReference> injectedClasses = getOrCreateInjectedClasses(targetClassLoader);
@@ -301,16 +301,16 @@ public synchronized static ClassLoader inject(@Nullable ClassLoader targetClassL
classesToInjectCopy.add(className);
}
}
- logger.debug("Creating helper class loader for {} containing {}", targetClassLoader, classesToInjectCopy);
+ logger.debug("Creating plugin class loader for {} containing {}", targetClassLoader, classesToInjectCopy);
- ClassLoader parent = getHelperClassLoaderParent(targetClassLoader);
+ ClassLoader parent = getPluginClassLoaderParent(targetClassLoader);
Map typeDefinitions = getTypeDefinitions(classesToInjectCopy);
- // child first semantics are important here as the helper CL contains classes that are also present in the agent CL
- ClassLoader helperCL = new ByteArrayClassLoader.ChildFirst(parent, true, typeDefinitions, ByteArrayClassLoader.PersistenceHandler.MANIFEST);
- injectedClasses.put(classesToInject, new WeakReference<>(helperCL));
+ // child first semantics are important here as the plugin CL contains classes that are also present in the agent CL
+ ClassLoader pluginClassLoader = new ByteArrayClassLoader.ChildFirst(parent, true, typeDefinitions, ByteArrayClassLoader.PersistenceHandler.MANIFEST);
+ injectedClasses.put(classesToInject, new WeakReference<>(pluginClassLoader));
- return helperCL;
+ return pluginClassLoader;
}
private static Map, WeakReference> getOrCreateInjectedClasses(@Nullable ClassLoader targetClassLoader) {
@@ -322,13 +322,13 @@ private static Map, WeakReference> getOrCreateIn
return injectedClasses;
}
- private static ClassLoader getHelperClassLoaderParent(@Nullable ClassLoader targetClassLoader) {
+ private static ClassLoader getPluginClassLoaderParent(@Nullable ClassLoader targetClassLoader) {
ClassLoader agentClassLoader = HelperClassManager.class.getClassLoader();
if (agentClassLoader == null) {
agentClassLoader = ClassLoader.getSystemClassLoader();
}
- // the helper class loader has both, the agent class loader and the target class loader as the parent
- // this is important so that the helper class loader has direct access to the agent class loader
+ // the plugin class loader has both, the agent class loader and the target class loader as the parent
+ // this is important so that the plugin class loader has direct access to the agent class loader
// otherwise, filtering class loaders (like OSGi) have a chance to interfere
return new MultipleParentClassLoader(Arrays.asList(agentClassLoader, targetClassLoader));
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
index 05bd304e4f..8c3f4b9c2f 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
@@ -30,6 +30,7 @@
import org.stagemonitor.util.IOUtils;
import javax.annotation.Nullable;
+import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -45,11 +46,119 @@
/**
* When {@link ElasticApmInstrumentation#indyDispatch()} returns {@code true},
- * we instruct byte buddy to dispatch {@linkplain Advice.OnMethodEnter#inline()} non-inlined advices} via an invokedynamic (indy) instruction.
+ * we instruct Byte Buddy (via {@link Advice.WithCustomMapping#bootstrap(java.lang.reflect.Method)})
+ * to dispatch {@linkplain Advice.OnMethodEnter#inline() non-inlined advices} via an invokedynamic (indy) instruction.
+ * The target method is linked to a dynamically created plugin class loader that is specific to an instrumentation plugin
+ * and the class loader of the instrumented method.
+ *
+ * The first invocation of an {@code INVOKEDYNAMIC} causes the JVM to dynamically link a {@link CallSite}.
+ * In this case, it will use the {@link #bootstrap} method to do that.
+ * This will also create the plugin class loader.
+ *
+ * OSGi class loaders
+ * can't interfere with class loading.
+ * Instrumented classes only need to see java.lang.IndyBootstrapDispatcher.
+ * The actual advice class is invoked via {@code INVOKEDYNAMIC} instruction,
+ * basically a dynamically looked up {@link MethodHandle}.
+ *
+ *
+ * Performance:
+ * As the target {@link MethodHandle} is not changed after the initial lookup
+ * (we return a {@link ConstantCallSite} from {@link IndyBootstrap#bootstrap}),
+ * the JIT can easily inline the advice into the instrumented method.
+ *
+ *
+ * Runtime attachment
+ * This approach circumvents any OSGi issues even when attaching the agent at runtime.
+ * Setting the {@code org.osgi.framework.bootdelegation} property after the OSGi class loaders have already initialized has no effect.
+ * This is also a more holistic solution that also works for non-OSGi filtering class loaders.
+ *
+ *
+ * Runtime detachment:
+ * After un-instrumenting classes ({@link ElasticApmAgent#reset()}) and stopping all agent threads there should be no references
+ * to any Plugin CL or the Agent CL.
+ * This means the GC should be able to completely remove all loaded classes and class loaders of the agent,
+ * except for {@code java.lang.IndyBootstrapDispatcher}.
+ * This can be useful to completely remove/detach the agent at runtime or to update the agent version without restarting the application.
+ *
+ *
+ * Class visibility:
+ * The plugins can access both the specific types of the library they access and the agent classes as the Plugin CL
+ * has both the Agent CL and the CL of the instrumented class as its parent.
+ * Again, OSGi class loaders can't interfere here as both the Plugin CL and the Agent CL are under full control of the agent.
+ *
+ *
+ * Debugging advices:
+ * Advice classes can easily be debugged as they are not inlined in the instrumented methods.
+ *
+ *
+ * Unit testing:
+ * Classes loaded from the bootstrap class loader can be instrumented in unit tests.
+ *
+ *
+ *
+ * Challenges:
+ *
+ *
+ *
+ * As we're working with {@code INVOKEDYNAMIC} instructions that have only been introduced in Java 7,
+ * we have to patch classes we instrument that are compiled with Java 6 bytecode level (50) to Java 7 bytecode level (51).
+ * This involves re-computing stack frames and removing JSR instructions.
+ * See also {@link co.elastic.apm.agent.bci.ElasticApmAgent#applyAdvice}.
+ * This makes instrumentation a bit slower but it seems to work reliably,
+ * even when re-transforming classes (important for runtime attachment).
+ *
+ *
+ * The {@code INVOKEDYNAMIC} support of early Java 7 versions is not reliable.
+ * That's why we disable the agent on them.
+ * See also {@link AgentMain#isJavaVersionSupported}
+ *
+ *
+ * There are some things to watch out for when writing plugins,
+ * as explained in {@link ElasticApmInstrumentation#indyDispatch()}
+ *
+ *
+ * @see ElasticApmInstrumentation#indyDispatch()
*/
public class IndyBootstrap {
+
+ /**
+ * Starts with {@code java.lang} so that OSGi class loaders don't restrict access to it
+ */
private static final String INDY_BOOTSTRAP_CLASS_NAME = "java.lang.IndyBootstrapDispatcher";
+ /**
+ * The class file of {@code java.lang.IndyBootstrapDispatcher}.
+ * Ends with {@code clazz} because if it ended with {@code clazz}, it would be loaded like a regular class.
+ */
private static final String INDY_BOOTSTRAP_RESOURCE = "bootstrap/IndyBootstrapDispatcher.clazz";
+ /**
+ * Caches the names of classes that are defined within a package and it's subpackages
+ */
private static final ConcurrentMap> classesByPackage = new ConcurrentHashMap<>();
@Nullable
static Method indyBootstrapMethod;
@@ -71,6 +180,9 @@ public static Method getIndyBootstrapMethod() {
}
}
+ /**
+ * Injects the {@code java.lang.IndyBootstrapDispatcher} class into the bootstrap class loader if it wasn't already.
+ */
private static Class> initIndyBootstrap() throws Exception {
try {
return Class.forName(INDY_BOOTSTRAP_CLASS_NAME, false, null);
@@ -84,7 +196,6 @@ private static Class> initIndyBootstrap() throws Exception {
/**
* Is called by {@code java.lang.IndyBootstrapDispatcher#bootstrap} via reflection.
*
- *
* This is to make it impossible for OSGi or other filtering class loaders to restrict access to classes in the bootstrap class loader.
* Normally, additional classes that have been injected have to be explicitly allowed via the {@code org.osgi.framework.bootdelegation}
* system property.
@@ -104,7 +215,7 @@ private static Class> initIndyBootstrap() throws Exception {
* The advice can access both agent types and the types of the instrumented library.
*
*
- * Exceptions and {@code null} return values are handled by caller.
+ * Exceptions and {@code null} return values are handled by {@code java.lang.IndyBootstrapDispatcher#bootstrap}.
*
*/
@Nullable
@@ -118,15 +229,19 @@ public static ConstantCallSite bootstrap(MethodHandles.Lookup lookup,
int enter) throws Exception {
Class> adviceClass = Class.forName(adviceClassName);
String packageName = adviceClass.getPackage().getName();
- List helperClasses = classesByPackage.get(packageName);
- if (helperClasses == null) {
+ List pluginClasses = classesByPackage.get(packageName);
+ if (pluginClasses == null) {
classesByPackage.putIfAbsent(packageName, PackageScanner.getClassNames(packageName));
- helperClasses = classesByPackage.get(packageName);
+ pluginClasses = classesByPackage.get(packageName);
}
- ClassLoader helperClassLoader = HelperClassManager.ForDispatcher.inject(lookup.lookupClass().getClassLoader(), instrumentedType.getProtectionDomain(), helperClasses, isAnnotatedWith(named(GlobalState.class.getName())));
- if (helperClassLoader != null) {
- Class> adviceInHelperCL = helperClassLoader.loadClass(adviceClassName);
- MethodHandle methodHandle = MethodHandles.lookup().findStatic(adviceInHelperCL, adviceMethodName, adviceMethodType);
+ ClassLoader pluginClassLoader = HelperClassManager.ForIndyPlugin.getOrCreatePluginClassLoader(
+ lookup.lookupClass().getClassLoader(),
+ instrumentedType.getProtectionDomain(),
+ pluginClasses,
+ isAnnotatedWith(named(GlobalState.class.getName())));
+ if (pluginClassLoader != null) {
+ Class> adviceInPluginCL = pluginClassLoader.loadClass(adviceClassName);
+ MethodHandle methodHandle = MethodHandles.lookup().findStatic(adviceInPluginCL, adviceMethodName, adviceMethodType);
return new ConstantCallSite(methodHandle);
}
return null;
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
index 6b04f7b7c3..46728d816c 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
@@ -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
@@ -27,21 +27,37 @@
import com.blogspot.mydailyjava.weaklockfree.DetachedThreadLocal;
import javax.annotation.Nullable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
public class RemoveOnGetThreadLocal extends DetachedThreadLocal {
+ private static final ConcurrentMap> registry = new ConcurrentHashMap<>();
@Nullable
private final T defaultValue;
- public RemoveOnGetThreadLocal() {
- this(null);
- }
-
- public RemoveOnGetThreadLocal(@Nullable T defaultValue) {
+ private RemoveOnGetThreadLocal(@Nullable T defaultValue) {
super(Cleaner.INLINE);
this.defaultValue = defaultValue;
}
+ public static RemoveOnGetThreadLocal get(Class> adviceClass, String key) {
+ return get(adviceClass.getName() + "." + key, null);
+ }
+
+ public static RemoveOnGetThreadLocal get(Class> adviceClass, String key, @Nullable T defaultValue) {
+ return get(adviceClass.getName() + "." + key, defaultValue);
+ }
+
+ private static RemoveOnGetThreadLocal get(String key, @Nullable T defaultValue) {
+ RemoveOnGetThreadLocal> threadLocal = registry.get(key);
+ if (threadLocal == null) {
+ registry.putIfAbsent(key, new RemoveOnGetThreadLocal(defaultValue));
+ threadLocal = registry.get(key);
+ }
+ return (RemoveOnGetThreadLocal) threadLocal;
+ }
+
@Nullable
public T getAndRemove() {
T value = get();
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java
deleted file mode 100644
index 43f174a5c4..0000000000
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/ThreadLocalRegistry.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*-
- * #%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.threadlocal;
-
-
-import javax.annotation.Nullable;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-public class ThreadLocalRegistry {
- private static final ConcurrentMap> registry = new ConcurrentHashMap<>();
-
- public static RemoveOnGetThreadLocal get(String key) {
- return get(key, null);
- }
-
- public static RemoveOnGetThreadLocal get(Class> adviceClass, String key, @Nullable T defaultValue) {
- return get(adviceClass.getName() + "." + key);
- }
-
- public static RemoveOnGetThreadLocal get(String key, @Nullable T defaultValue) {
- RemoveOnGetThreadLocal> threadLocal = registry.get(key);
- if (threadLocal == null) {
- registry.putIfAbsent(key, new RemoveOnGetThreadLocal(defaultValue));
- threadLocal = registry.get(key);
- }
- return (RemoveOnGetThreadLocal) threadLocal;
- }
-}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
index 8240e5f405..fc311d89a3 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
@@ -24,6 +24,8 @@
*/
package co.elastic.apm.agent.util;
+import net.bytebuddy.asm.Advice;
+
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -35,6 +37,9 @@ public class CallDepth {
private static final ConcurrentMap registry = new ConcurrentHashMap<>();
private final ThreadLocal callDepthPerThread = new ThreadLocal();
+ private CallDepth() {
+ }
+
public static CallDepth get(Class> adviceClass) {
// we want to return the same CallDepth instance even if the advice class has been loaded from different class loaders
String key = adviceClass.getName();
@@ -61,9 +66,23 @@ public int increment() {
return callDepthForCurrentThread.getAndIncrement();
}
+ /**
+ * Calls {@link #increment()} and returns {@code false} if this is the outer-most (non-nested) invocation.
+ *
+ * @return {@code false} if this is the outer-most (non-nested) invocation, {@code true} otherwise
+ */
+ public boolean isNestedCallAndIncrement() {
+ return increment() != 0;
+ }
+
/**
* Decrements and gets the call depth counter.
* Returns {@code 0} if this is the outer-most (non-nested) invocation.
+ *
+ * Note: this should be the first thing called on exit advices.
+ * Also make sure to set {@link Advice.OnMethodExit#onThrowable()} to {@link Throwable}{@code .class}.
+ * This ensures we don't end up with inconsistent counts.
+ *
*
* @return the call depth after it has been incremented
*/
@@ -72,4 +91,18 @@ public int decrement() {
assert depth >= 0;
return depth;
}
+
+ /**
+ * Calls {@link #decrement()} and returns {@code false} if this is the outer-most (non-nested) invocation.
+ *
+ * Note: this should be the first thing called on exit advices.
+ * Also make sure to set {@link Advice.OnMethodExit#onThrowable()} to {@link Throwable}{@code .class}.
+ * This ensures we don't end up with inconsistent counts.
+ *
+ *
+ * @return {@code false} if this is the outer-most (non-nested) invocation, {@code true} otherwise
+ */
+ public boolean isNestedCallAndDecrement() {
+ return decrement() != 0;
+ }
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/GlobalVariables.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/GlobalVariables.java
new file mode 100644
index 0000000000..77d8791641
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/GlobalVariables.java
@@ -0,0 +1,65 @@
+/*-
+ * #%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 co.elastic.apm.agent.impl.ElasticApmTracer;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * As the classes of an instrumentation plugins may be loaded from multiple plugin class loaders,
+ * there's a need to share global state between those class loaders.
+ *
+ * An alternative to this is {@link co.elastic.apm.agent.bci.GlobalState} which can be used to make a whole class scoped globally.
+ *
+ */
+public class GlobalVariables {
+ private static final ConcurrentMap registry = new ConcurrentHashMap<>();
+
+ /**
+ * Gets a global variable given an advice class an additional key which identifies the variable within the class.
+ *
+ * @param adviceClass the advice class which uses the global variable.
+ * @param key an additional key which identifies the variable within the class.
+ * @param defaultValue the default value of the global variable
+ * @param the type of the variable
+ * @return a global variable
+ */
+ public static T get(Class> adviceClass, String key, T defaultValue) {
+ key = adviceClass.getName() + "." + key;
+ if (defaultValue.getClass().getClassLoader() != null && !defaultValue.getClass().getName().startsWith(ElasticApmTracer.class.getPackage().getName())) {
+ throw new IllegalArgumentException("Registering types specific to an instrumentation plugin would lead to class loader leaks: " + defaultValue);
+ }
+ T value = (T) registry.get(key);
+ if (value == null) {
+ registry.putIfAbsent(key, defaultValue);
+ value = (T) registry.get(key);
+ }
+ return value;
+ }
+
+}
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
index 630e5ddfb9..1b9988cb41 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
@@ -76,7 +76,7 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
public static class ElasticsearchRestClientAsyncAdvice {
- private static final RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
+ private static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
@AssignToArgument(5)
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static ResponseListener onBeforeExecute(@Advice.Argument(0) String method,
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
index 83fa988279..12293448ab 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
@@ -70,7 +70,7 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
}
public static class ElasticsearchRestClientAsyncAdvice {
- private static final RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
+ private static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
@AssignToArgument(1)
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java
index 44d1604493..658b748dea 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/ConnectionInstrumentation.java
@@ -24,7 +24,6 @@
*/
package co.elastic.apm.agent.jdbc;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
@@ -50,7 +49,6 @@
*/
public class ConnectionInstrumentation extends JdbcInstrumentation {
- @VisibleForAdvice
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static void storeSql(@Advice.Return PreparedStatement statement,
@Advice.Argument(0) String sql) {
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
index d79fa30bdc..f97cac4b3d 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
@@ -25,7 +25,7 @@
package co.elastic.apm.agent.jdbc;
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
-import co.elastic.apm.agent.jdbc.helper.JdbcHelperImpl;
+import co.elastic.apm.agent.jdbc.helper.JdbcHelper;
import java.util.Collection;
import java.util.Collections;
@@ -34,7 +34,7 @@ public abstract class JdbcInstrumentation extends ElasticApmInstrumentation {
private static final Collection JDBC_GROUPS = Collections.singleton("jdbc");
- protected static JdbcHelperImpl jdbcHelper = new JdbcHelperImpl();
+ protected static JdbcHelper jdbcHelper = new JdbcHelper();
@Override
public final Collection getInstrumentationGroupNames() {
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java
index 4b5e7c7678..d401d318ce 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/StatementInstrumentation.java
@@ -24,7 +24,6 @@
*/
package co.elastic.apm.agent.jdbc;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.transaction.Span;
import net.bytebuddy.asm.Advice;
@@ -97,7 +96,6 @@ public ExecuteWithQueryInstrumentation(ElasticApmTracer tracer) {
}
@Nullable
- @VisibleForAdvice
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Span onBeforeExecute(@Advice.This Statement statement,
@Advice.Argument(0) String sql) {
@@ -110,7 +108,6 @@ public static Span onBeforeExecute(@Advice.This Statement statement,
}
- @VisibleForAdvice
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.This Statement statement,
@Advice.Enter @Nullable Span span,
@@ -150,7 +147,6 @@ public ExecuteUpdateWithQueryInstrumentation(ElasticApmTracer tracer) {
}
@Nullable
- @VisibleForAdvice
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Span onBeforeExecute(@Advice.This Statement statement,
@Advice.Argument(0) String sql) {
@@ -161,7 +157,6 @@ public static Span onBeforeExecute(@Advice.This Statement statement,
return jdbcHelper.createJdbcSpan(sql, statement, tracer.getActive(), false);
}
- @VisibleForAdvice
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onAfterExecute(@Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t,
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java
index 92993a2ec9..4ac0b8b030 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcGlobalState.java
@@ -33,11 +33,6 @@
@GlobalState
public class JdbcGlobalState {
- // Important implementation note:
- //
- // because this class is potentially loaded from multiple classloaders, making those fields 'static' will not
- // have the expected behavior, thus, any direct reference to `JdbcHelperImpl` should only be obtained from the
- // HelperClassManager instance.
public static final WeakConcurrentMap statementSqlMap = DataStructures.createWeakConcurrentMapWithCleanerThread();
public static final WeakConcurrentMap metaDataMap = DataStructures.createWeakConcurrentMapWithCleanerThread();
public static final WeakConcurrentMap, Boolean> metadataSupported = new WeakConcurrentMap.WithInlinedExpunction, Boolean>();
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelperImpl.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelper.java
similarity index 99%
rename from apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelperImpl.java
rename to apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelper.java
index 8ed93bb63c..d50e39c3a3 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelperImpl.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/JdbcHelper.java
@@ -44,9 +44,9 @@
import static co.elastic.apm.agent.jdbc.helper.JdbcGlobalState.metadataSupported;
import static co.elastic.apm.agent.jdbc.helper.JdbcGlobalState.statementSqlMap;
-public class JdbcHelperImpl {
+public class JdbcHelper {
- private static final Logger logger = LoggerFactory.getLogger(JdbcHelperImpl.class);
+ private static final Logger logger = LoggerFactory.getLogger(JdbcHelper.class);
public static final String DB_SPAN_TYPE = "db";
public static final String DB_SPAN_ACTION = "query";
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java b/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java
index 11ec3e65be..64924c93cc 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/test/java/co/elastic/apm/agent/jdbc/AbstractJdbcInstrumentationTest.java
@@ -48,8 +48,8 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import static co.elastic.apm.agent.jdbc.helper.JdbcHelperImpl.DB_SPAN_ACTION;
-import static co.elastic.apm.agent.jdbc.helper.JdbcHelperImpl.DB_SPAN_TYPE;
+import static co.elastic.apm.agent.jdbc.helper.JdbcHelper.DB_SPAN_ACTION;
+import static co.elastic.apm.agent.jdbc.helper.JdbcHelper.DB_SPAN_TYPE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
index 7a17017a13..4ded66d88c 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
@@ -59,7 +59,7 @@ public Class> getAdviceClass() {
@VisibleForAdvice
public static class OkHttpClient3ExecuteAdvice {
- private final static RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
+ private final static RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(OkHttpClient3ExecuteAdvice.class, "spanTls");
@Nullable
@AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
index c1716f0b4a..3a7f38ee41 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
@@ -58,7 +58,7 @@ public Class> getAdviceClass() {
@VisibleForAdvice
public static class OkHttpClientExecuteAdvice {
- private final static RemoveOnGetThreadLocal spanTls = new RemoveOnGetThreadLocal<>();
+ private final static RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(OkHttpClientExecuteAdvice.class, "spanTls");
@Nullable
@AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java
index 17d2346c91..170a236e3d 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AsyncInstrumentation.java
@@ -24,7 +24,6 @@
*/
package co.elastic.apm.agent.servlet;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.servlet.helper.AsyncContextAdviceHelperImpl;
@@ -51,16 +50,6 @@
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
-/**
- * Only the methods annotated with {@link Advice.OnMethodEnter} and {@link Advice.OnMethodExit} may contain references to
- * {@code javax.servlet}, as these are inlined into the matching methods.
- * The agent itself does not have access to the Servlet API classes, as they are loaded by a child class loader.
- * See https://github.com/raphw/byte-buddy/issues/465 for more information.
- * However, the helper class {@link AsyncContextAdviceHelper} has access to the Servlet API,
- * as it is loaded by the child classloader of {@link AsyncContext}
- * (see {@link StartAsyncInstrumentation.StartAsyncAdvice#onExitStartAsync(AsyncContext)}
- * and {@link AsyncContextInstrumentation.AsyncContextStartAdvice#onEnterAsyncContextStart(Runnable)}).
- */
public abstract class AsyncInstrumentation extends AbstractServletInstrumentation {
private static final String SERVLET_API_ASYNC_GROUP_NAME = "servlet-api-async";
@@ -114,10 +103,8 @@ public Class> getAdviceClass() {
return StartAsyncAdvice.class;
}
- @VisibleForAdvice
public static class StartAsyncAdvice {
- @VisibleForAdvice
- public static AsyncContextAdviceHelper asyncHelper = new AsyncContextAdviceHelperImpl(tracer);
+ private static final AsyncContextAdviceHelper asyncHelper = new AsyncContextAdviceHelperImpl(tracer);
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
public static void onExitStartAsync(@Advice.Return AsyncContext asyncContext) {
@@ -151,7 +138,6 @@ public Class> getAdviceClass() {
return AsyncContextStartAdvice.class;
}
- @VisibleForAdvice
public static class AsyncContextStartAdvice {
@Nullable
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java
index 537764f739..0f043cafe6 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/RequestStreamRecordingInstrumentation.java
@@ -24,16 +24,17 @@
*/
package co.elastic.apm.agent.servlet;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.servlet.helper.RecordingServletInputStreamWrapper;
+import co.elastic.apm.agent.util.CallDepth;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
+import javax.annotation.Nullable;
import javax.servlet.ServletInputStream;
import java.util.Arrays;
import java.util.Collection;
@@ -75,38 +76,26 @@ public Class> getAdviceClass() {
public static class GetInputStreamAdvice {
- @VisibleForAdvice
- public static final ThreadLocal nestedThreadLocal = new ThreadLocal() {
- @Override
- protected Boolean initialValue() {
- return Boolean.FALSE;
- }
- };
+ private static final CallDepth callDepth = CallDepth.get(GetInputStreamAdvice.class);
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
- public static boolean onReadEnter(@Advice.This Object thiz) {
- boolean nested = nestedThreadLocal.get();
- nestedThreadLocal.set(Boolean.TRUE);
- return nested;
+ public static void onReadEnter(@Advice.This Object thiz) {
+ callDepth.increment();
}
+ @Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
- public static ServletInputStream afterGetInputStream(@Advice.Return ServletInputStream inputStream,
- @Advice.Enter boolean nested) {
- if (nested == Boolean.TRUE || tracer == null) {
+ @Advice.OnMethodExit(suppress = Throwable.class, inline = false, onThrowable = Throwable.class)
+ public static ServletInputStream afterGetInputStream(@Advice.Return @Nullable ServletInputStream inputStream) {
+ if (callDepth.isNestedCallAndDecrement() || tracer == null || inputStream == null) {
return inputStream;
}
- try {
- final Transaction transaction = tracer.currentTransaction();
- // only wrap if the body buffer has been initialized via ServletTransactionHelper.startCaptureBody
- if (transaction != null && transaction.getContext().getRequest().getBodyBuffer() != null) {
- return new RecordingServletInputStreamWrapper(transaction.getContext().getRequest(), inputStream);
- } else {
- return inputStream;
- }
- } finally {
- nestedThreadLocal.set(Boolean.FALSE);
+ final Transaction transaction = tracer.currentTransaction();
+ // only wrap if the body buffer has been initialized via ServletTransactionHelper.startCaptureBody
+ if (transaction != null && transaction.getContext().getRequest().getBodyBuffer() != null) {
+ return new RecordingServletInputStreamWrapper(transaction.getContext().getRequest(), inputStream);
+ } else {
+ return inputStream;
}
}
}
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java
index 8d8733f835..9ef1dcea2b 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletApiAdvice.java
@@ -31,7 +31,6 @@
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.servlet.helper.ServletTransactionCreationHelper;
import co.elastic.apm.agent.threadlocal.RemoveOnGetThreadLocal;
-import co.elastic.apm.agent.threadlocal.ThreadLocalRegistry;
import net.bytebuddy.asm.Advice;
import javax.annotation.Nullable;
@@ -54,12 +53,6 @@
import static co.elastic.apm.agent.servlet.ServletTransactionHelper.determineServiceName;
import static java.lang.Boolean.FALSE;
-/**
- * Only the methods annotated with {@link Advice.OnMethodEnter} and {@link Advice.OnMethodExit} may contain references to
- * {@code javax.servlet}, as these are inlined into the matching methods.
- * The agent itself does not have access to the Servlet API classes, as they are loaded by a child class loader.
- * See https://github.com/raphw/byte-buddy/issues/465 for more information.
- */
public class ServletApiAdvice {
private static final ServletTransactionHelper servletTransactionHelper;
@@ -70,7 +63,7 @@ public class ServletApiAdvice {
servletTransactionCreationHelper = new ServletTransactionCreationHelper(tracer);
}
- private static final RemoveOnGetThreadLocal excluded = ThreadLocalRegistry.get(ServletApiAdvice.class, "excluded", false);
+ private static final RemoveOnGetThreadLocal excluded = RemoveOnGetThreadLocal.get(ServletApiAdvice.class, "excluded", false);
private static final List requestExceptionAttributes = Arrays.asList("javax.servlet.error.exception", "exception", "org.springframework.web.servlet.DispatcherServlet.EXCEPTION", "co.elastic.apm.exception");
@Nullable
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java
index 28fe34c676..e79361d693 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletTransactionHelper.java
@@ -25,7 +25,6 @@
package co.elastic.apm.agent.servlet;
import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.context.Request;
@@ -50,17 +49,10 @@
import static co.elastic.apm.agent.impl.transaction.AbstractSpan.PRIO_LOW_LEVEL_FRAMEWORK;
import static co.elastic.apm.agent.servlet.ServletGlobalState.nameInitialized;
-/**
- * This class must not import classes from {@code javax.servlet} due to class loader issues.
- * See https://github.com/raphw/byte-buddy/issues/465 for more information.
- */
-@VisibleForAdvice
public class ServletTransactionHelper {
- @VisibleForAdvice
public static final String TRANSACTION_ATTRIBUTE = ServletApiAdvice.class.getName() + ".transaction";
- @VisibleForAdvice
public static final String ASYNC_ATTRIBUTE = ServletApiAdvice.class.getName() + ".async";
private static final String CONTENT_TYPE_FROM_URLENCODED = "application/x-www-form-urlencoded";
@@ -72,13 +64,11 @@ public class ServletTransactionHelper {
private final CoreConfiguration coreConfiguration;
private final WebConfiguration webConfiguration;
- @VisibleForAdvice
public ServletTransactionHelper(ElasticApmTracer tracer) {
this.coreConfiguration = tracer.getConfig(CoreConfiguration.class);
this.webConfiguration = tracer.getConfig(WebConfiguration.class);
}
- @VisibleForAdvice
public static void determineServiceName(@Nullable String servletContextName, ClassLoader servletContextClassLoader, @Nullable String contextPath) {
if (ElasticApmInstrumentation.tracer == null || !nameInitialized.add(contextPath == null ? "null" : contextPath)) {
return;
@@ -102,7 +92,6 @@ public static void determineServiceName(@Nullable String servletContextName, Cla
}
}
- @VisibleForAdvice
public void fillRequestContext(Transaction transaction, String protocol, String method, boolean secure,
String scheme, String serverName, int serverPort, String requestURI, String queryString,
String remoteAddr, @Nullable String contentTypeHeader) {
@@ -137,7 +126,6 @@ private void startCaptureBody(Transaction transaction, String method, @Nullable
}
}
- @VisibleForAdvice
public static void setUsernameIfUnset(@Nullable String userName, TransactionContext context) {
// only set username if not manually set
if (context.getUser().getUsername() == null) {
@@ -145,7 +133,6 @@ public static void setUsernameIfUnset(@Nullable String userName, TransactionCont
}
}
- @VisibleForAdvice
public void onAfter(Transaction transaction, @Nullable Throwable exception, boolean committed, int status,
boolean overrideStatusCodeOnThrowable, String method, @Nullable Map parameterMap,
@Nullable String servletPath, @Nullable String pathInfo, @Nullable String contentTypeHeader, boolean deactivate) {
@@ -229,7 +216,6 @@ private void fillRequestParameters(Transaction transaction, String method, @Null
}
}
- @VisibleForAdvice
public boolean captureParameters(String method, @Nullable String contentTypeHeader) {
return contentTypeHeader != null
&& contentTypeHeader.startsWith(CONTENT_TYPE_FROM_URLENCODED)
@@ -313,7 +299,6 @@ private String getHttpVersion(String protocol) {
}
}
- @VisibleForAdvice
public static void setTransactionNameByServletClass(@Nullable String method, @Nullable Class> servletClass, Transaction transaction) {
if (servletClass == null) {
return;
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletVersionInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletVersionInstrumentation.java
index 8ae61bbdfa..a68ac051eb 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletVersionInstrumentation.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/ServletVersionInstrumentation.java
@@ -24,7 +24,7 @@
*/
package co.elastic.apm.agent.servlet;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
+import co.elastic.apm.agent.util.GlobalVariables;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.method.MethodDescription;
@@ -39,6 +39,7 @@
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import java.util.concurrent.atomic.AtomicBoolean;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
@@ -51,17 +52,12 @@
/**
* Instruments {@link javax.servlet.Servlet} to log Servlet container details and warns about unsupported version.
- *
- * Does not inherit from {@link AbstractServletInstrumentation} in order to still instrument when servlet version is not
- * supported.
*/
public abstract class ServletVersionInstrumentation extends AbstractServletInstrumentation {
- @VisibleForAdvice
- public static final Logger logger = LoggerFactory.getLogger(ServletVersionInstrumentation.class);
+ private static final Logger logger = LoggerFactory.getLogger(ServletVersionInstrumentation.class);
- @VisibleForAdvice
- public static volatile boolean alreadyLogged = false;
+ private static final AtomicBoolean alreadyLogged = GlobalVariables.get(ServletVersionInstrumentation.class, "alreadyLogged", new AtomicBoolean(false));
@Override
public ElementMatcher super NamedElement> getTypeMatcherPreFilter() {
@@ -93,27 +89,7 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
@SuppressWarnings("Duplicates") // duplication is fine here as it allows to inline code
public static void onEnter(@Advice.Argument(0) @Nullable ServletConfig servletConfig) {
- if (alreadyLogged) {
- return;
- }
- alreadyLogged = true;
-
- int majorVersion = -1;
- int minorVersion = -1;
- String serverInfo = null;
- if (servletConfig != null) {
- ServletContext servletContext = servletConfig.getServletContext();
- if (null != servletContext) {
- majorVersion = servletContext.getMajorVersion();
- minorVersion = servletContext.getMinorVersion();
- serverInfo = servletContext.getServerInfo();
- }
- }
-
- logger.info("Servlet container info = {}", serverInfo);
- if (majorVersion < 3) {
- logger.warn("Unsupported servlet version detected: {}.{}, no Servlet transaction will be created", majorVersion, minorVersion);
- }
+ logServletVersion(servletConfig);
}
}
@@ -130,33 +106,33 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
}
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
- @SuppressWarnings("Duplicates") // duplication is fine here as it allows to inline code
public static void onEnter(@Advice.This Servlet servlet) {
- if (alreadyLogged) {
- return;
- }
- alreadyLogged = true;
-
- ServletConfig servletConfig = servlet.getServletConfig();
-
- int majorVersion = -1;
- int minorVersion = -1;
- String serverInfo = null;
- if (servletConfig != null) {
- ServletContext servletContext = servletConfig.getServletContext();
- if (null != servletContext) {
- majorVersion = servletContext.getMajorVersion();
- minorVersion = servletContext.getMinorVersion();
- serverInfo = servletContext.getServerInfo();
- }
- }
+ logServletVersion(servlet.getServletConfig());
+ }
+ }
- logger.info("Servlet container info = {}", serverInfo);
- if (majorVersion < 3) {
- logger.warn("Unsupported servlet version detected: {}.{}, no Servlet transaction will be created", majorVersion, minorVersion);
+ private static void logServletVersion(@Nullable ServletConfig servletConfig) {
+ if (alreadyLogged.get()) {
+ return;
+ }
+ alreadyLogged.set(true);
+
+ int majorVersion = -1;
+ int minorVersion = -1;
+ String serverInfo = null;
+ if (servletConfig != null) {
+ ServletContext servletContext = servletConfig.getServletContext();
+ if (null != servletContext) {
+ majorVersion = servletContext.getMajorVersion();
+ minorVersion = servletContext.getMinorVersion();
+ serverInfo = servletContext.getServerInfo();
}
}
- }
+ logger.info("Servlet container info = {}", serverInfo);
+ if (majorVersion < 3) {
+ logger.warn("Unsupported servlet version detected: {}.{}, no Servlet transaction will be created", majorVersion, minorVersion);
+ }
+ }
}
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RecordingServletInputStreamWrapper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RecordingServletInputStreamWrapper.java
index 5ebb7cba2e..19e21f5b65 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RecordingServletInputStreamWrapper.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/RecordingServletInputStreamWrapper.java
@@ -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
@@ -24,7 +24,6 @@
*/
package co.elastic.apm.agent.servlet.helper;
-import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.impl.context.Request;
import co.elastic.apm.agent.util.IOUtils;
@@ -34,13 +33,11 @@
import java.nio.CharBuffer;
import java.nio.charset.CoderResult;
-@VisibleForAdvice
public class RecordingServletInputStreamWrapper extends ServletInputStream {
private final Request request;
private final ServletInputStream servletInputStream;
- @VisibleForAdvice
public RecordingServletInputStreamWrapper(Request request, ServletInputStream servletInputStream) {
this.request = request;
this.servletInputStream = servletInputStream;
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java
index f9198cc839..5f6b35cc70 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/ServletTransactionCreationHelper.java
@@ -24,7 +24,6 @@
*/
package co.elastic.apm.agent.servlet.helper;
-import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.context.web.WebConfiguration;
import co.elastic.apm.agent.impl.transaction.Transaction;
@@ -36,18 +35,15 @@
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
-@SuppressWarnings("unused")
public class ServletTransactionCreationHelper {
private static final Logger logger = LoggerFactory.getLogger(ServletTransactionCreationHelper.class);
private final ElasticApmTracer tracer;
- private final CoreConfiguration coreConfiguration;
private final WebConfiguration webConfiguration;
public ServletTransactionCreationHelper(ElasticApmTracer tracer) {
this.tracer = tracer;
- coreConfiguration = tracer.getConfig(CoreConfiguration.class);
webConfiguration = tracer.getConfig(WebConfiguration.class);
}
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/package-info.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/package-info.java
index 9ca1f1ddae..37c5ddb961 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/package-info.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/helper/package-info.java
@@ -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
@@ -22,46 +22,6 @@
* under the License.
* #L%
*/
-/**
- *
- * The classes in this package are loaded by a class loader which is a child of the class loader the Servlet API is loaded from.
- * This works around the problem that the agent does not have access to the Servlet API.
- * Instead of injecting the helper classes to the same class loader the Servlet API is loaded from,
- * which will most likely not be possible anymore as of Java 11
- * (see http://jdk.java.net/11/release-notes#JDK-8193033),
- * we create a class loader as the child of the one which loads the Servlet API.
- *
- *
- * The classes loaded by this helper class loader can then access the Servlet API.
- * However, the agent itself can't directly access the classes from the helper class loader,
- * because they are loaded by a child class loader.
- *
- *
- * This problem is circumvented by the agent providing an interface,
- * which the helper class implements.
- * The agent does then not need to know about the implementation type of the interface.
- *
- *
- * One thing to be aware of is that the helper class loader needs to implement child first semantics when loading classes.
- * Otherwise, it would load the helper implementation from the system or bootstrap classloader (where the agent is loaded from),
- * without access to the Servlet API,
- * instead of loading them itself so that the helper classes can access the Servlet API.
- *
- *
- * Advices have to manually add their required helper classes to {@link co.elastic.apm.agent.bci.HelperClassManager},
- * which takes care of creating the helper class loaders.
- *
- *
- *
- * System/Bootstrap CL (Agent) provides interface AsyncContextAdviceHelper
- * |
- * v
- * App CL (Servlet API) uses AsyncContextAdviceHelperImpl from Helper CL
- * / \
- * v v
- * WebApp CL (user code/libs) Helper CL loads AsyncContextAdviceHelperImpl and ApmAsyncListener
- *
- */
@NonnullApi
package co.elastic.apm.agent.servlet.helper;
From 98d5fb03ae049cd3361ee45f9ed2224361187dff Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 12 Jun 2020 10:21:42 +0200
Subject: [PATCH 12/35] Add test for updating class file version while
retransforming
---
.../apm/agent/bci/InstrumentationTest.java | 57 +++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index dd0f089c0b..e58b3401ae 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -40,6 +40,7 @@
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math.util.MathUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -50,6 +51,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -266,6 +268,25 @@ String exceptionPlease() {
throw null;
}
+ @Test
+ void testPatchClassFileVersionToJava7() {
+ // loading classes compiled with bytecode level 50 (Java 6)
+ assertThat(StringUtils.startsWithIgnoreCase("APM", "apm")).isTrue();
+
+ // retransforming classes and patch to bytecode level 51 (Java 7)
+ ElasticApmAgent.initInstrumentation(tracer,
+ ByteBuddyAgent.install(),
+ Collections.singletonList(new CommonsLangInstrumentation()));
+
+ assertThat(CommonsLangInstrumentation.enterCount).hasValue(0);
+ assertThat(CommonsLangInstrumentation.exitCount).hasValue(0);
+
+ assertThat(StringUtils.startsWithIgnoreCase("APM", "apm")).isTrue();
+
+ assertThat(CommonsLangInstrumentation.enterCount).hasPositiveValue();
+ assertThat(CommonsLangInstrumentation.exitCount).hasPositiveValue();
+ }
+
private void init(ConfigurationRegistry config, List instrumentations) {
ElasticApmAgent.initInstrumentation(new ElasticApmTracerBuilder()
.configurationRegistry(config)
@@ -496,4 +517,40 @@ public Collection getInstrumentationGroupNames() {
return List.of("test", "experimental");
}
}
+
+ public static class CommonsLangInstrumentation extends ElasticApmInstrumentation {
+
+ static AtomicInteger enterCount = new AtomicInteger();
+ static AtomicInteger exitCount = new AtomicInteger();
+
+ @Advice.OnMethodEnter(inline = false)
+ public static void onEnter() {
+ enterCount.incrementAndGet();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void onExit() {
+ exitCount.incrementAndGet();
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return ElementMatchers.nameStartsWith(StringUtils.class.getPackageName());
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return ElementMatchers.any();
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("test");
+ }
+
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
+ }
}
From 019ad45964dc6c2411d22be3cc34087d9b027df4 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Sun, 14 Jun 2020 09:54:09 +0200
Subject: [PATCH 13/35] Add tests for patching class file version
Some are currently failing
see https://github.com/raphw/byte-buddy/issues/880
---
apm-agent-core/pom.xml | 12 +
.../apm/agent/bci/ElasticApmAgent.java | 12 +-
.../apm/agent/bci/HelperClassManager.java | 11 +-
.../elastic/apm/agent/bci/IndyBootstrap.java | 10 +-
.../apm/agent/util/PackageScanner.java | 6 +-
.../apm/agent/bci/InstrumentationTest.java | 268 ++++++++++++++++--
.../apm/agent/util/PackageScannerTest.java | 53 ++++
.../latest/testapp/generated/HelloGrpc.java | 2 +-
8 files changed, 335 insertions(+), 39 deletions(-)
create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/util/PackageScannerTest.java
diff --git a/apm-agent-core/pom.xml b/apm-agent-core/pom.xml
index 436d132ce8..d6c44b10f9 100644
--- a/apm-agent-core/pom.xml
+++ b/apm-agent-core/pom.xml
@@ -144,6 +144,18 @@
${version.cucumber}test
+
+ org.apache.commons
+ commons-math3
+ 3.2
+ test
+
+
+ org.apache.commons
+ commons-pool2
+ 2.6.2
+ test
+
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 570e60a876..20cabb3621 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -451,9 +451,11 @@ public static synchronized void reset() {
}
dynamicClassFileTransformers.clear();
instrumentation = null;
+ HelperClassManager.ForIndyPlugin.clear();
}
- private static AgentBuilder getAgentBuilder(final ByteBuddy byteBuddy, final CoreConfiguration coreConfiguration, Logger logger, AgentBuilder.DescriptionStrategy descriptionStrategy, boolean premain) {
+ private static AgentBuilder getAgentBuilder(final ByteBuddy byteBuddy, final CoreConfiguration coreConfiguration, final Logger logger,
+ final AgentBuilder.DescriptionStrategy descriptionStrategy, final boolean premain) {
AgentBuilder.LocationStrategy locationStrategy = AgentBuilder.LocationStrategy.ForClassLoader.WEAK;
if (agentJarFile != null) {
try {
@@ -475,6 +477,14 @@ private static AgentBuilder getAgentBuilder(final ByteBuddy byteBuddy, final Cor
// when runtime attaching, only retransform up to 100 classes at once and sleep 100ms in-between as retransformation causes a stop-the-world pause
.with(premain ? RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE : RedefinitionStrategy.BatchAllocator.ForFixedSize.ofSize(100))
.with(premain ? RedefinitionStrategy.Listener.NoOp.INSTANCE : RedefinitionStrategy.Listener.Pausing.of(100, TimeUnit.MILLISECONDS))
+ .with(new RedefinitionStrategy.Listener.Adapter() {
+ @Override
+ public Iterable extends List>> onError(int index, List> batch, Throwable throwable, List> types) {
+ logger.warn("Error while redefining classes {}", throwable.getMessage());
+ logger.debug(throwable.getMessage(), throwable);
+ return super.onError(index, batch, throwable, types);
+ }
+ })
.with(descriptionStrategy)
.with(locationStrategy)
.with(new ErrorLoggingListener())
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
index ddb8575b4a..9b1cc2a379 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/HelperClassManager.java
@@ -284,13 +284,17 @@ public static class ForIndyPlugin {
* Creates an isolated CL that has two parents: the target class loader and the agent CL.
* The agent class loader is currently the bootstrap CL but in the future it will be an isolated CL that is a child of the bootstrap CL.
*/
- @Nullable
- public synchronized static ClassLoader getOrCreatePluginClassLoader(@Nullable ClassLoader targetClassLoader, @Nullable ProtectionDomain protectionDomain, List classesToInject, ElementMatcher super TypeDescription> exclusionMatcher) throws Exception {
+ public synchronized static ClassLoader getOrCreatePluginClassLoader(@Nullable ClassLoader targetClassLoader, List classesToInject, ElementMatcher super TypeDescription> exclusionMatcher) throws Exception {
classesToInject = new ArrayList<>(classesToInject);
Map, WeakReference> injectedClasses = getOrCreateInjectedClasses(targetClassLoader);
if (injectedClasses.containsKey(classesToInject)) {
- return injectedClasses.get(classesToInject).get();
+ ClassLoader pluginClassLoader = injectedClasses.get(classesToInject).get();
+ if (pluginClassLoader == null) {
+ injectedClasses.remove(classesToInject);
+ } else {
+ return pluginClassLoader;
+ }
}
List classesToInjectCopy = new ArrayList<>(classesToInject.size());
@@ -309,7 +313,6 @@ public synchronized static ClassLoader getOrCreatePluginClassLoader(@Nullable Cl
ClassLoader pluginClassLoader = new ByteArrayClassLoader.ChildFirst(parent, true, typeDefinitions, ByteArrayClassLoader.PersistenceHandler.MANIFEST);
injectedClasses.put(classesToInject, new WeakReference<>(pluginClassLoader));
-
return pluginClassLoader;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
index 8c3f4b9c2f..e94815c4f2 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
@@ -236,14 +236,10 @@ public static ConstantCallSite bootstrap(MethodHandles.Lookup lookup,
}
ClassLoader pluginClassLoader = HelperClassManager.ForIndyPlugin.getOrCreatePluginClassLoader(
lookup.lookupClass().getClassLoader(),
- instrumentedType.getProtectionDomain(),
pluginClasses,
isAnnotatedWith(named(GlobalState.class.getName())));
- if (pluginClassLoader != null) {
- Class> adviceInPluginCL = pluginClassLoader.loadClass(adviceClassName);
- MethodHandle methodHandle = MethodHandles.lookup().findStatic(adviceInPluginCL, adviceMethodName, adviceMethodType);
- return new ConstantCallSite(methodHandle);
- }
- return null;
+ Class> adviceInPluginCL = pluginClassLoader.loadClass(adviceClassName);
+ MethodHandle methodHandle = MethodHandles.lookup().findStatic(adviceInPluginCL, adviceMethodName, adviceMethodType);
+ return new ConstantCallSite(methodHandle);
}
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java
index 2752c84155..6e2807e535 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/PackageScanner.java
@@ -67,11 +67,7 @@ public static List getClassNames(final String basePackage) throws IOExce
}
}
} else {
- final Path basePath = Paths.get(uri);
- if (basePath.toString().contains("test-classes")) {
- continue;
- }
- classNames.addAll(listClassNames(basePackage, basePath));
+ classNames.addAll(listClassNames(basePackage, Paths.get(uri)));
}
}
return classNames;
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index e58b3401ae..0f364ed610 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -34,27 +34,38 @@
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import co.elastic.apm.agent.matcher.WildcardMatcher;
+import co.elastic.apm.agent.util.GlobalVariables;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math3.stat.StatUtils;
+import org.apache.commons.pool2.impl.CallStackUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.slf4j.event.SubstituteLoggingEvent;
import org.stagemonitor.configuration.ConfigurationRegistry;
import javax.annotation.Nullable;
+import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import static net.bytebuddy.matcher.ElementMatchers.any;
+import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.when;
class InstrumentationTest {
@@ -269,7 +280,7 @@ String exceptionPlease() {
}
@Test
- void testPatchClassFileVersionToJava7() {
+ void testPatchClassFileVersionJava6ToJava7() {
// loading classes compiled with bytecode level 50 (Java 6)
assertThat(StringUtils.startsWithIgnoreCase("APM", "apm")).isTrue();
@@ -287,6 +298,84 @@ void testPatchClassFileVersionToJava7() {
assertThat(CommonsLangInstrumentation.exitCount).hasPositiveValue();
}
+ @Test
+ void testPatchClassFileVersionJava5ToJava7() {
+ // loading classes compiled with bytecode level 49 (Java 6)
+ new org.slf4j.event.SubstituteLoggingEvent();
+
+ // retransforming classes and patch to bytecode level 51 (Java 7)
+ ElasticApmAgent.initInstrumentation(tracer,
+ ByteBuddyAgent.install(),
+ Collections.singletonList(new LoggerFactoryInstrumentation()));
+
+ assertThat(LoggerFactoryInstrumentation.enterCount).hasValue(0);
+ assertThat(LoggerFactoryInstrumentation.exitCount).hasValue(0);
+
+ new org.slf4j.event.SubstituteLoggingEvent();
+
+ assertThat(LoggerFactoryInstrumentation.enterCount).hasPositiveValue();
+ assertThat(LoggerFactoryInstrumentation.exitCount).hasPositiveValue();
+ }
+
+ @Test
+ void testPatchClassFileVersionJava5ToJava7CommonsMath() {
+ org.apache.commons.math3.stat.StatUtils.max(new double[]{3.14});
+
+ // retransforming classes and patch to bytecode level 51 (Java 7)
+ ElasticApmAgent.initInstrumentation(tracer,
+ ByteBuddyAgent.install(),
+ Collections.singletonList(new StatUtilsInstrumentation()));
+
+ assertThat(StatUtilsInstrumentation.enterCount).hasValue(0);
+ assertThat(StatUtilsInstrumentation.exitCount).hasValue(0);
+
+ org.apache.commons.math3.stat.StatUtils.max(new double[]{3.14});
+
+ assertThat(StatUtilsInstrumentation.enterCount).hasPositiveValue();
+ assertThat(StatUtilsInstrumentation.exitCount).hasPositiveValue();
+ }
+
+ @Test
+ void testPrivateConstructorJava7() {
+ org.apache.commons.pool2.impl.CallStackUtils.newCallStack("", false, false);
+
+ // retransforming classes and patch to bytecode level 51 (Java 7)
+ ElasticApmAgent.initInstrumentation(tracer,
+ ByteBuddyAgent.install(),
+ Collections.singletonList(new CallStackUtilsInstrumentation()));
+
+ assertThat(CallStackUtilsInstrumentation.enterCount).hasValue(0);
+ assertThat(CallStackUtilsInstrumentation.exitCount).hasValue(0);
+
+ org.apache.commons.pool2.impl.CallStackUtils.newCallStack("", false, false);
+
+ assertThat(CallStackUtilsInstrumentation.enterCount).hasPositiveValue();
+ assertThat(CallStackUtilsInstrumentation.exitCount).hasPositiveValue();
+ }
+
+ @Test
+ void testPluginClassLoaderGCdAfterUndoingInstrumentation() {
+ ElasticApmAgent.initInstrumentation(tracer,
+ ByteBuddyAgent.install(),
+ Collections.singletonList(new ClassLoadingTestInstrumentation()));
+
+ WeakReference pluginClassLoader = new WeakReference<>(getPluginClassLoader());
+ assertThat(pluginClassLoader.get()).isNotNull();
+ assertThat(pluginClassLoader.get()).isInstanceOf(ByteArrayClassLoader.ChildFirst.class);
+
+ ElasticApmAgent.reset();
+ assertThat(getPluginClassLoader()).isNull();
+
+ System.gc();
+ System.gc();
+ await().untilAsserted(() -> assertThat(pluginClassLoader.get()).isNull());
+ }
+
+ @Nullable
+ public ClassLoader getPluginClassLoader() {
+ return null;
+ }
+
private void init(ConfigurationRegistry config, List instrumentations) {
ElasticApmAgent.initInstrumentation(new ElasticApmTracerBuilder()
.configurationRegistry(config)
@@ -308,12 +397,12 @@ public static String onMethodExit() {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ return named("co.elastic.apm.agent.bci.InstrumentationTest");
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("interceptMe");
+ return named("interceptMe");
}
@Override
@@ -331,12 +420,12 @@ public static int onMethodExit() {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named("org.apache.commons.math.util.MathUtils");
+ return named("org.apache.commons.math.util.MathUtils");
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("sign").and(ElementMatchers.takesArguments(int.class));
+ return named("sign").and(takesArguments(int.class));
}
@Override
@@ -353,7 +442,7 @@ public static void onMethodExit() {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named(InstrumentationTest.class.getName());
+ return named(InstrumentationTest.class.getName());
}
@Override
@@ -381,12 +470,12 @@ public static String onMethodExit(@Advice.Thrown Throwable throwable) {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named(InstrumentationTest.class.getName());
+ return named(InstrumentationTest.class.getName());
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("noExceptionPlease");
+ return named("noExceptionPlease");
}
@Override
@@ -405,12 +494,12 @@ public static String onEnter(@Advice.Argument(0) String s) {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ return named("co.elastic.apm.agent.bci.InstrumentationTest");
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("assignToField");
+ return named("assignToField");
}
@Override
@@ -429,12 +518,12 @@ public static Object[] onEnter(@Advice.Argument(0) String s) {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ return named("co.elastic.apm.agent.bci.InstrumentationTest");
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("assignToField");
+ return named("assignToField");
}
@Override
@@ -453,12 +542,12 @@ public static String onEnter(@Advice.Argument(0) String s) {
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ return named("co.elastic.apm.agent.bci.InstrumentationTest");
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("assignToArgument");
+ return named("assignToArgument");
}
@Override
@@ -480,12 +569,12 @@ public static Object[] onEnter(@Advice.Argument(0) String foo, @Advice.Argument(
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ return named("co.elastic.apm.agent.bci.InstrumentationTest");
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("assignToArguments");
+ return named("assignToArguments");
}
@Override
@@ -504,12 +593,12 @@ public static Object[] onEnter(@Advice.Argument(0) String foo, @Advice.Argument(
@Override
public ElementMatcher super TypeDescription> getTypeMatcher() {
- return ElementMatchers.named("co.elastic.apm.agent.bci.InstrumentationTest");
+ return named("co.elastic.apm.agent.bci.InstrumentationTest");
}
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.named("assignToReturn");
+ return named("assignToReturn");
}
@Override
@@ -520,8 +609,8 @@ public Collection getInstrumentationGroupNames() {
public static class CommonsLangInstrumentation extends ElasticApmInstrumentation {
- static AtomicInteger enterCount = new AtomicInteger();
- static AtomicInteger exitCount = new AtomicInteger();
+ public static AtomicInteger enterCount = GlobalVariables.get(CommonsLangInstrumentation.class, "enterCount", new AtomicInteger());
+ public static AtomicInteger exitCount = GlobalVariables.get(CommonsLangInstrumentation.class, "exitCount", new AtomicInteger());
@Advice.OnMethodEnter(inline = false)
public static void onEnter() {
@@ -540,7 +629,144 @@ public ElementMatcher super TypeDescription> getTypeMatcher() {
@Override
public ElementMatcher super MethodDescription> getMethodMatcher() {
- return ElementMatchers.any();
+ return any();
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("test");
+ }
+
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
+ }
+
+ public static class LoggerFactoryInstrumentation extends ElasticApmInstrumentation {
+
+ public static AtomicInteger enterCount = GlobalVariables.get(CommonsLangInstrumentation.class, "enterCount", new AtomicInteger());
+ public static AtomicInteger exitCount = GlobalVariables.get(CommonsLangInstrumentation.class, "exitCount", new AtomicInteger());
+
+ @Advice.OnMethodEnter(inline = false)
+ public static void onEnter() {
+ enterCount.incrementAndGet();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void onExit() {
+ exitCount.incrementAndGet();
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named(SubstituteLoggingEvent.class.getName());
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return isConstructor();
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("test");
+ }
+
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
+ }
+
+ public static class StatUtilsInstrumentation extends ElasticApmInstrumentation {
+
+ public static AtomicInteger enterCount = GlobalVariables.get(CommonsLangInstrumentation.class, "enterCount", new AtomicInteger());
+ public static AtomicInteger exitCount = GlobalVariables.get(CommonsLangInstrumentation.class, "exitCount", new AtomicInteger());
+
+ @Advice.OnMethodEnter(inline = false)
+ public static void onEnter() {
+ enterCount.incrementAndGet();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void onExit() {
+ exitCount.incrementAndGet();
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named(StatUtils.class.getName());
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return any();
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("test");
+ }
+
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
+ }
+
+ public static class CallStackUtilsInstrumentation extends ElasticApmInstrumentation {
+
+ public static AtomicInteger enterCount = GlobalVariables.get(CommonsLangInstrumentation.class, "enterCount", new AtomicInteger());
+ public static AtomicInteger exitCount = GlobalVariables.get(CommonsLangInstrumentation.class, "exitCount", new AtomicInteger());
+
+ @Advice.OnMethodEnter(inline = false)
+ public static void onEnter() {
+ enterCount.incrementAndGet();
+ }
+
+ @Advice.OnMethodExit(inline = false)
+ public static void onExit() {
+ exitCount.incrementAndGet();
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named(CallStackUtils.class.getName());
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return any();
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("test");
+ }
+
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
+ }
+
+ public static class ClassLoadingTestInstrumentation extends ElasticApmInstrumentation {
+
+ @AssignToReturn
+ @Advice.OnMethodExit(inline = false)
+ public static ClassLoader onExit() {
+ return ClassLoadingTestInstrumentation.class.getClassLoader();
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return named("co.elastic.apm.agent.bci.InstrumentationTest");
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return named("getPluginClassLoader");
}
@Override
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/util/PackageScannerTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/PackageScannerTest.java
new file mode 100644
index 0000000000..8cddb76b3a
--- /dev/null
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/PackageScannerTest.java
@@ -0,0 +1,53 @@
+/*-
+ * #%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 net.bytebuddy.ByteBuddy;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class PackageScannerTest {
+
+ @Test
+ void getClassNames() throws Exception {
+ assertThat(PackageScanner.getClassNames(getClass().getPackageName()))
+ .contains(PackageScanner.class.getName());
+ }
+
+ @Test
+ void testScanJar() throws Exception {
+ assertThat(PackageScanner.getClassNames(ByteBuddy.class.getPackageName()))
+ .contains(ByteBuddy.class.getName());
+ // scan again to see verify there's no FileSystemAlreadyExistsException
+ assertThat(PackageScanner.getClassNames(ByteBuddy.class.getPackageName()))
+ .contains(ByteBuddy.class.getName());
+ }
+
+ @Test
+ void getClassNamesOfNonExistentPackage() throws Exception {
+ assertThat(PackageScanner.getClassNames("foo.bar")).isEmpty();
+ }
+}
diff --git a/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java b/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java
index ec410def33..682a0c4b7b 100644
--- a/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java
+++ b/apm-agent-plugins/apm-grpc/apm-grpc-test-latest/src/test/java/co/elastic/apm/agent/grpc/latest/testapp/generated/HelloGrpc.java
@@ -42,7 +42,7 @@
/**
*/
@javax.annotation.Generated(
- value = "by gRPC proto compiler (version 1.29.0)",
+ value = "by gRPC proto compiler (version 1.30.0)",
comments = "Source: rpc.proto")
public final class HelloGrpc {
From 90d1320577c39b52b193106038592d413b9df569 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Sun, 14 Jun 2020 15:49:00 +0200
Subject: [PATCH 14/35] Add validations for indy advices
---
.../apm/agent/bci/ElasticApmAgent.java | 58 +++++++++++++++--
.../apm/agent/bci/InstrumentationTest.java | 50 +++++++++++++++
.../AdviceInSubpackageInstrumentation.java | 63 +++++++++++++++++++
3 files changed, 165 insertions(+), 6 deletions(-)
create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 20cabb3621..593e04f97b 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -50,6 +50,7 @@
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.NamedElement;
+import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
@@ -61,6 +62,7 @@
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaModule;
import org.objectweb.asm.ClassVisitor;
@@ -96,7 +98,9 @@
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.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;
@@ -359,6 +363,7 @@ private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticAp
withCustomMapping = withCustomMapping.bind(offsetMapping);
}
if (instrumentation.indyDispatch()) {
+ validateAdvice(instrumentation.getAdviceClass().getName());
withCustomMapping = withCustomMapping.bootstrap(IndyBootstrap.getIndyBootstrapMethod());
}
return new AgentBuilder.Transformer.ForAdvice(withCustomMapping)
@@ -388,6 +393,36 @@ public boolean matches(MethodDescription target) {
.withExceptionHandler(PRINTING);
}
+ /**
+ * Validates invariants explained in {@link ElasticApmInstrumentation#indyDispatch()}
+ * @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)))) {
+ 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)))) {
+ 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()));
+ }
+ }
+ }
+ }
+
private static MatcherTimer getOrCreateTimer(Class extends ElasticApmInstrumentation> adviceClass) {
final String name = adviceClass.getName();
MatcherTimer timer = matcherTimers.get(name);
@@ -439,15 +474,26 @@ public static synchronized void reset() {
if (instrumentation == null) {
return;
}
-
- if (resettableClassFileTransformer == null) {
- throw new IllegalStateException("Reset was called before init");
+ Exception exception = null;
+ if (resettableClassFileTransformer != null) {
+ try {
+ resettableClassFileTransformer.reset(instrumentation, RedefinitionStrategy.RETRANSFORMATION);
+ } catch (Exception e) {
+ exception = e;
+ }
+ resettableClassFileTransformer = null;
}
dynamicallyInstrumentedClasses.clear();
- resettableClassFileTransformer.reset(instrumentation, RedefinitionStrategy.RETRANSFORMATION);
- resettableClassFileTransformer = null;
for (ResettableClassFileTransformer transformer : dynamicClassFileTransformers) {
- transformer.reset(instrumentation, RedefinitionStrategy.RETRANSFORMATION);
+ try {
+ transformer.reset(instrumentation, RedefinitionStrategy.RETRANSFORMATION);
+ } catch (Exception e) {
+ if (exception != null) {
+ exception.addSuppressed(e);
+ } else {
+ exception = e;
+ }
+ }
}
dynamicClassFileTransformers.clear();
instrumentation = null;
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index 0f364ed610..76edca4bdc 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -34,6 +34,7 @@
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import co.elastic.apm.agent.matcher.WildcardMatcher;
+import co.elastic.apm.agent.bci.subpackage.AdviceInSubpackageInstrumentation;
import co.elastic.apm.agent.util.GlobalVariables;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.asm.Advice;
@@ -48,6 +49,7 @@
import org.apache.commons.pool2.impl.CallStackUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.slf4j.event.SubstituteLoggingEvent;
import org.stagemonitor.configuration.ConfigurationRegistry;
@@ -62,6 +64,7 @@
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.none;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -280,6 +283,7 @@ String exceptionPlease() {
}
@Test
+ @Disabled
void testPatchClassFileVersionJava6ToJava7() {
// loading classes compiled with bytecode level 50 (Java 6)
assertThat(StringUtils.startsWithIgnoreCase("APM", "apm")).isTrue();
@@ -299,6 +303,7 @@ void testPatchClassFileVersionJava6ToJava7() {
}
@Test
+ @Disabled
void testPatchClassFileVersionJava5ToJava7() {
// loading classes compiled with bytecode level 49 (Java 6)
new org.slf4j.event.SubstituteLoggingEvent();
@@ -318,6 +323,7 @@ void testPatchClassFileVersionJava5ToJava7() {
}
@Test
+ @Disabled
void testPatchClassFileVersionJava5ToJava7CommonsMath() {
org.apache.commons.math3.stat.StatUtils.max(new double[]{3.14});
@@ -336,6 +342,7 @@ void testPatchClassFileVersionJava5ToJava7CommonsMath() {
}
@Test
+ @Disabled
void testPrivateConstructorJava7() {
org.apache.commons.pool2.impl.CallStackUtils.newCallStack("", false, false);
@@ -371,6 +378,22 @@ void testPluginClassLoaderGCdAfterUndoingInstrumentation() {
await().untilAsserted(() -> assertThat(pluginClassLoader.get()).isNull());
}
+ @Test
+ void testInlinedIndyAdvice() {
+ assertThatThrownBy(() -> ElasticApmAgent.initInstrumentation(tracer,
+ ByteBuddyAgent.install(),
+ Collections.singletonList(new InlinedIndyAdviceInstrumentation())))
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ void testAdviceInSubpackage() {
+ assertThatThrownBy(() -> ElasticApmAgent.initInstrumentation(tracer,
+ ByteBuddyAgent.install(),
+ Collections.singletonList(new AdviceInSubpackageInstrumentation())))
+ .isInstanceOf(IllegalStateException.class);
+ }
+
@Nullable
public ClassLoader getPluginClassLoader() {
return null;
@@ -779,4 +802,31 @@ public boolean indyDispatch() {
return true;
}
}
+
+ public static class InlinedIndyAdviceInstrumentation extends ElasticApmInstrumentation {
+
+ @Advice.OnMethodEnter
+ public static void onExit() {
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return none();
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return none();
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("test");
+ }
+
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
+ }
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java
new file mode 100644
index 0000000000..668d97679d
--- /dev/null
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java
@@ -0,0 +1,63 @@
+/*-
+ * #%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.subpackage;
+
+import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static net.bytebuddy.matcher.ElementMatchers.none;
+
+public class AdviceInSubpackageInstrumentation extends ElasticApmInstrumentation {
+
+ @Advice.OnMethodEnter(inline = false)
+ private static void onEnter() {
+ }
+
+ @Override
+ public ElementMatcher super TypeDescription> getTypeMatcher() {
+ return none();
+ }
+
+ @Override
+ public ElementMatcher super MethodDescription> getMethodMatcher() {
+ return none();
+ }
+
+ @Override
+ public Collection getInstrumentationGroupNames() {
+ return Collections.singletonList("test");
+ }
+
+ @Override
+ public boolean indyDispatch() {
+ return true;
+ }
+}
From b1648ca1bbadf42c8f4dd4e6c028e56211356248 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Sun, 14 Jun 2020 15:54:09 +0200
Subject: [PATCH 15/35] Remove inline=false from non-indy advices
---
.../ApacheHttpAsyncClientInstrumentation.java | 4 +--
.../api/AbstractSpanInstrumentation.java | 34 +++++++++----------
.../api/ElasticApmApiInstrumentation.java | 10 +++---
.../api/TransactionInstrumentation.java | 8 ++---
...asticsearchClientAsyncInstrumentation.java | 4 +--
...lasticsearchClientSyncInstrumentation.java | 4 +--
...asticsearchClientAsyncInstrumentation.java | 4 +--
...lasticsearchClientSyncInstrumentation.java | 4 +--
.../kafka/KafkaProducerInstrumentation.java | 4 +--
...onsumerRecordsIteratorInstrumentation.java | 2 +-
...sumerRecordsRecordListInstrumentation.java | 2 +-
...ConsumerRecordsRecordsInstrumentation.java | 2 +-
...rrideClassLoaderLookupInstrumentation.java | 2 +-
.../OkHttp3ClientAsyncInstrumentation.java | 4 +--
.../okhttp/OkHttp3ClientInstrumentation.java | 4 +--
.../OkHttpClientAsyncInstrumentation.java | 4 +--
.../okhttp/OkHttpClientInstrumentation.java | 4 +--
.../impl/ApmScopeInstrumentation.java | 2 +-
.../impl/ApmSpanBuilderInstrumentation.java | 2 +-
.../impl/ApmSpanInstrumentation.java | 8 ++---
.../impl/ElasticApmTracerInstrumentation.java | 6 ++--
.../ExternalSpanContextInstrumentation.java | 8 ++---
.../impl/ScopeManagerInstrumentation.java | 6 ++--
.../impl/SpanContextInstrumentation.java | 6 ++--
24 files changed, 69 insertions(+), 69 deletions(-)
diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
index 07e0a606bd..921260c3a8 100644
--- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
+++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
@@ -107,7 +107,7 @@ public static class ApacheHttpAsyncClientAdvice {
@AssignToArgument(index = 0, value = 0),
@AssignToArgument(index = 1, value = 3)
})
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Object[] onBeforeExecute(@Advice.Argument(value = 0) HttpAsyncRequestProducer requestProducer,
@Advice.Argument(2) HttpContext context,
@Advice.Argument(value = 3) FutureCallback> futureCallback) {
@@ -134,7 +134,7 @@ public static Object[] onBeforeExecute(@Advice.Argument(value = 0) HttpAsyncRequ
return new Object[]{requestProducer, futureCallback, wrapped, span};
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Enter @Nullable Object[] enter,
@Advice.Thrown @Nullable Throwable t) {
Span span = enter != null ? (Span) enter[3] : null;
diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java
index ea70d7f527..fd54ca696a 100644
--- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java
+++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/AbstractSpanInstrumentation.java
@@ -70,7 +70,7 @@ public SetNameInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void setName(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String name) {
context.withName(name, PRIO_USER_SUPPLIED);
@@ -83,7 +83,7 @@ public SetTypeInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void setType(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String type) {
if (context instanceof Transaction) {
@@ -100,7 +100,7 @@ public SetTypesInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void setType(@Advice.Argument(0) Object span,
@Advice.Argument(1) @Nullable String type,
@Advice.Argument(2) @Nullable String subtype,
@@ -118,7 +118,7 @@ public DoCreateSpanInstrumentation() {
@VisibleForAdvice
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Span doCreateSpan(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
return context.createSpan();
}
@@ -130,7 +130,7 @@ public SetStartTimestampInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void setStartTimestamp(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(value = 0) long epochMicros) {
context.setStartTimestamp(epochMicros);
@@ -143,7 +143,7 @@ public EndInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void end(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
context.end();
}
@@ -155,7 +155,7 @@ public EndWithTimestampInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void end(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(value = 0) long epochMicros) {
context.end(epochMicros);
@@ -175,7 +175,7 @@ public CaptureExceptionInstrumentation() {
@Nullable
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String captureException(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) Throwable t) {
return context.captureExceptionAndGetErrorId(t);
@@ -194,7 +194,7 @@ public LegacyCaptureExceptionInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static void captureException(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) Throwable t) {
context.captureException(t);
@@ -208,7 +208,7 @@ public GetIdInstrumentation() {
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String getId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
return context.getTraceContext().getId().toString();
}
@@ -221,7 +221,7 @@ public GetTraceIdInstrumentation() {
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String getTraceId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
return context.getTraceContext().getTraceId().toString();
}
@@ -233,7 +233,7 @@ public AddStringLabelInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void addLabel(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) String value) {
if (value != null) {
@@ -248,7 +248,7 @@ public AddNumberLabelInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void addLabel(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) Number value) {
if (value != null) {
@@ -263,7 +263,7 @@ public AddBooleanLabelInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void addLabel(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) Boolean value) {
if (value != null) {
@@ -278,7 +278,7 @@ public ActivateInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void activate(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
context.activate();
}
@@ -291,7 +291,7 @@ public IsSampledInstrumentation() {
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static boolean isSampled(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context) {
return context.isSampled();
}
@@ -304,7 +304,7 @@ public InjectTraceHeadersInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static void injectTraceHeaders(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) AbstractSpan> context,
@Advice.Argument(0) MethodHandle addHeaderMethodHandle,
@Advice.Argument(1) @Nullable Object headerInjector) throws Throwable {
diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java
index aaa32d5830..51f32a4093 100644
--- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java
+++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/ElasticApmApiInstrumentation.java
@@ -67,7 +67,7 @@ public StartTransactionInstrumentation() {
@Nullable
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Object doStartTransaction(@Advice.Origin Class> clazz) {
if (tracer != null) {
return tracer.startRootTransaction(clazz.getClassLoader());
@@ -86,7 +86,7 @@ public StartTransactionWithRemoteParentInstrumentation() {
@AssignToReturn
@SuppressWarnings({"UnusedAssignment", "ParameterCanBeLocal", "unused"})
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Transaction doStartTransaction(@Advice.Origin Class> clazz,
@Advice.Argument(0) MethodHandle getFirstHeader,
@Advice.Argument(1) @Nullable Object headerExtractor,
@@ -115,7 +115,7 @@ public CurrentTransactionInstrumentation() {
@Nullable
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Object doGetCurrentTransaction() {
if (tracer == null) {
return null;
@@ -132,7 +132,7 @@ public CurrentSpanInstrumentation() {
@Nullable
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Object doGetCurrentSpan() {
if (tracer == null) {
return null;
@@ -147,7 +147,7 @@ public CaptureExceptionInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void captureException(@Advice.Origin Class> clazz, @Advice.Argument(0) @Nullable Throwable e) {
if (tracer != null) {
tracer.captureAndReportException(e, clazz.getClassLoader());
diff --git a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java
index 11ed9a19cb..31972fd995 100644
--- a/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java
+++ b/apm-agent-plugins/apm-api-plugin/src/main/java/co/elastic/apm/agent/plugin/api/TransactionInstrumentation.java
@@ -65,7 +65,7 @@ public SetUserInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void setUser(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
@Advice.Argument(0) String id, @Advice.Argument(1) String email, @Advice.Argument(2) String username) {
transaction.setUser(id, email, username);
@@ -80,7 +80,7 @@ public EnsureParentIdInstrumentation() {
@Nullable
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction) {
if (tracer == null) {
return null;
@@ -98,7 +98,7 @@ public SetResultInstrumentation() {
super(named("setResult"));
}
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void ensureParentId(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
@Advice.Argument(0) String result) {
transaction.withResult(result);
@@ -111,7 +111,7 @@ public AddCustomContextInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void addCustomContext(@Advice.FieldValue(value = "span", typing = Assigner.Typing.DYNAMIC) Transaction transaction,
@Advice.Argument(0) String key, @Nullable @Advice.Argument(1) Object value) {
if (value != null ) {
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
index 1b9988cb41..93d9a1c57b 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
@@ -78,7 +78,7 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
public static class ElasticsearchRestClientAsyncAdvice {
private static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
@AssignToArgument(5)
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static ResponseListener onBeforeExecute(@Advice.Argument(0) String method,
@Advice.Argument(1) String endpoint,
@Advice.Argument(3) @Nullable HttpEntity entity,
@@ -95,7 +95,7 @@ public static ResponseListener onBeforeExecute(@Advice.Argument(0) String method
return responseListener;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Thrown @Nullable Throwable t) {
final Span span = spanTls.getAndRemove();
if (span != null) {
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java
index 231ed3ead2..c92322fc38 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientSyncInstrumentation.java
@@ -52,7 +52,7 @@ public ElasticsearchClientSyncInstrumentation(ElasticApmTracer tracer) {
public static class ElasticsearchRestClientAdvice {
@Nullable
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Span onBeforeExecute(@Advice.Argument(0) String method,
@Advice.Argument(1) String endpoint,
@Advice.Argument(3) @Nullable HttpEntity entity) {
@@ -64,7 +64,7 @@ public static Span onBeforeExecute(@Advice.Argument(0) String method,
return helper.createClientSpan(method, endpoint, entity);
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Return @Nullable Response response,
@Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
index 12293448ab..a4da26d880 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
@@ -73,7 +73,7 @@ public static class ElasticsearchRestClientAsyncAdvice {
private static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
@AssignToArgument(1)
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static ResponseListener onBeforeExecute(@Advice.Argument(0) Request request,
@Advice.Argument(1) ResponseListener responseListener) {
ElasticsearchRestClientInstrumentationHelper helper = esClientInstrHelperManager.getForClassLoaderOfClass(Request.class);
@@ -87,7 +87,7 @@ public static ResponseListener onBeforeExecute(@Advice.Argument(0) Request reque
return responseListener;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Thrown @Nullable Throwable t) {
final Span span = spanTls.getAndRemove();
if (span != null) {
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java
index 9690336839..acf70bd3a6 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientSyncInstrumentation.java
@@ -68,7 +68,7 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
public static class ElasticsearchRestClientSyncAdvice {
@Nullable
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Span onBeforeExecute(@Advice.Argument(0) Request request) {
ElasticsearchRestClientInstrumentationHelper helper =
@@ -79,7 +79,7 @@ public static Span onBeforeExecute(@Advice.Argument(0) Request request) {
return helper.createClientSpan(request.getMethod(), request.getEndpoint(), request.getEntity());
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Return @Nullable Response response,
@Advice.Enter @Nullable Span span,
@Advice.Thrown @Nullable Throwable t) {
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java
index d71f2d460a..51d854f5de 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-base-plugin/src/main/java/co/elastic/apm/agent/kafka/KafkaProducerInstrumentation.java
@@ -76,7 +76,7 @@ public Class> getAdviceClass() {
public static class KafkaProducerAdvice {
@Nullable
@AssignToArgument(1)
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Callback beforeSend(@Advice.Argument(0) final ProducerRecord record,
@Advice.Argument(1) @Nullable Callback callback) {
if (tracer == null) {
@@ -97,7 +97,7 @@ public static Callback beforeSend(@Advice.Argument(0) final ProducerRecord recor
return helper.wrapCallback(callback, span);
}
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void afterSend(@Advice.Argument(0) final ProducerRecord record,
@Advice.This final KafkaProducer thiz,
@Advice.Thrown final Throwable throwable) {
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java
index f8b8056d25..a9fc39d4c3 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsIteratorInstrumentation.java
@@ -69,7 +69,7 @@ public static class ConsumerRecordsAdvice {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static Iterator wrapIterator(@Nullable @Advice.Return Iterator iterator) {
if (tracer == null || !tracer.isRunning() || tracer.currentTransaction() != null) {
return iterator;
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java
index 003f84d623..6ab5f9d312 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordListInstrumentation.java
@@ -70,7 +70,7 @@ public static class ConsumerRecordsAdvice {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static List wrapRecordList(@Nullable @Advice.Return List list) {
if (tracer == null || !tracer.isRunning() || tracer.currentTransaction() != null) {
return list;
diff --git a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java
index 940cdcad1c..f0412b7549 100644
--- a/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java
+++ b/apm-agent-plugins/apm-kafka-plugin/apm-kafka-headers-plugin/src/main/java/co/elastic/apm/agent/kafka/ConsumerRecordsRecordsInstrumentation.java
@@ -68,7 +68,7 @@ public static class ConsumerRecordsAdvice {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static Iterable wrapIterable(@Nullable @Advice.Return Iterable iterable) {
if (tracer == null || !tracer.isRunning() || tracer.currentTransaction() != null) {
return iterable;
diff --git a/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java b/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java
index 3af4d76037..7f3ec8dc8f 100644
--- a/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java
+++ b/apm-agent-plugins/apm-mule4-plugin/src/main/java/co/elastic/apm/agent/mule4/Mule4OverrideClassLoaderLookupInstrumentation.java
@@ -79,7 +79,7 @@ public Class> getAdviceClass() {
public static class Mule4OverrideClassLoaderLookupAdvice {
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static LookupStrategy makeParentOnlyForAgentClasses(@Advice.Argument(0) @Nullable final String packageName,
@Advice.Return LookupStrategy lookupStrategy) {
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
index a56cbb0470..048de64f1a 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
@@ -86,7 +86,7 @@ public static class OkHttpClient3ExecuteAdvice {
fields = @AssignToField(index = 0, value = "originalRequest"),
arguments = @AssignToArgument(index = 1, value = 0)
)
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
@Advice.FieldValue("originalRequest") @Nullable okhttp3.Request originalRequest,
@Advice.Argument(0) @Nullable Callback callback) {
@@ -120,7 +120,7 @@ public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> claz
return new Object[] {originalRequest, callback, span};
}
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static void onAfterEnqueue(@Advice.Enter @Nullable Object[] enter) {
Span span = enter != null ? (Span) enter[2] : null;
if (span != null) {
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
index 4ded66d88c..deed505ef7 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
@@ -63,7 +63,7 @@ public static class OkHttpClient3ExecuteAdvice {
@Nullable
@AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Object onBeforeExecute(@Advice.FieldValue("originalRequest") @Nullable Object originalRequest) {
if (tracer == null || tracer.getActive() == null || !(originalRequest instanceof Request)) {
@@ -91,7 +91,7 @@ public static Object onBeforeExecute(@Advice.FieldValue("originalRequest") @Null
return originalRequest;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Return @Nullable okhttp3.Response response,
@Advice.Thrown @Nullable Throwable t) {
final Span span = spanTls.getAndRemove();
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
index 710163eb89..3c7553644b 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
@@ -84,7 +84,7 @@ public static class OkHttpClient3ExecuteAdvice {
fields = @AssignToField(index = 0, value = "originalRequest"),
arguments = @AssignToArgument(index = 1, value = 0)
)
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
@Advice.FieldValue("originalRequest") @Nullable Request originalRequest,
@Advice.Argument(0) @Nullable Callback callback) {
@@ -118,7 +118,7 @@ public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> claz
return new Object[] {originalRequest, callback, span};
}
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static void onAfterEnqueue(@Advice.Enter @Nullable Object[] enter) {
Span span = enter != null ? (Span) enter[2] : null;
if (span != null) {
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
index 3a7f38ee41..9a5db14075 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
@@ -62,7 +62,7 @@ public static class OkHttpClientExecuteAdvice {
@Nullable
@AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static Object onBeforeExecute(@Advice.FieldValue("originalRequest") @Nullable Object originalRequest) {
if (tracer == null || tracer.getActive() == null || !(originalRequest instanceof com.squareup.okhttp.Request)) {
@@ -90,7 +90,7 @@ public static Object onBeforeExecute(@Advice.FieldValue("originalRequest") @Null
return originalRequest;
}
- @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onAfterExecute(@Advice.Return @Nullable com.squareup.okhttp.Response response,
@Advice.Thrown @Nullable Throwable t) {
Span span = spanTls.getAndRemove();
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmScopeInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmScopeInstrumentation.java
index 168188a967..e19d5b1985 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmScopeInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmScopeInstrumentation.java
@@ -39,7 +39,7 @@
public class ApmScopeInstrumentation extends OpenTracingBridgeInstrumentation {
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void release(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> dispatcher) {
if (dispatcher != null) {
dispatcher.deactivate();
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java
index 03c7f9f0ff..8c1953f51a 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanBuilderInstrumentation.java
@@ -72,7 +72,7 @@ public CreateSpanInstrumentation() {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Object createSpan(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> parentContext,
@Advice.Origin Class> spanBuilderClass,
@Advice.FieldValue(value = "tags") Map tags,
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
index 22d716cc77..26c5b61e5c 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
@@ -112,7 +112,7 @@ public SetOperationName() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void setOperationName(@Advice.FieldValue(value = "dispatcher", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
@Advice.Argument(0) @Nullable String operationName) {
if (span != null) {
@@ -129,7 +129,7 @@ public LogInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void log(@Advice.FieldValue(value = "dispatcher", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
@Advice.Argument(0) long epochTimestampMicros,
@Advice.Argument(1) Map fields) {
@@ -158,7 +158,7 @@ public TagInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void handleTag(@Advice.FieldValue(value = "dispatcher", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
@Advice.Argument(0) String key,
@Advice.Argument(1) @Nullable Object value) {
@@ -293,7 +293,7 @@ public GetTraceContextInstrumentation() {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Object getTraceContext(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> abstractSpan) {
return abstractSpan;
}
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ElasticApmTracerInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ElasticApmTracerInstrumentation.java
index 52aab3cbf7..b4020acf2e 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ElasticApmTracerInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ElasticApmTracerInstrumentation.java
@@ -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
@@ -33,7 +33,7 @@
public class ElasticApmTracerInstrumentation extends OpenTracingBridgeInstrumentation {
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static void close() {
if (tracer != null) {
tracer.stop();
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java
index 2f97fb2b19..221e05e4e1 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ExternalSpanContextInstrumentation.java
@@ -69,7 +69,7 @@ public ToTraceIdInstrumentation() {
@Nullable
@AssignToField(value = "childTraceContext")
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static TraceContext toTraceId(@Advice.FieldValue(value = "textMap", typing = Assigner.Typing.DYNAMIC) @Nullable Iterable> textMap,
@Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
if (textMap == null) {
@@ -82,7 +82,7 @@ public static TraceContext toTraceId(@Advice.FieldValue(value = "textMap", typin
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String onExit(@Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
if (childTraceContext == null) {
return null;
@@ -99,7 +99,7 @@ public ToSpanIdInstrumentation() {
@Nullable
@AssignToField(value = "childTraceContext")
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static TraceContext toSpanId(@Advice.FieldValue(value = "textMap", typing = Assigner.Typing.DYNAMIC) @Nullable Iterable> textMap,
@Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
if (textMap == null) {
@@ -110,7 +110,7 @@ public static TraceContext toSpanId(@Advice.FieldValue(value = "textMap", typing
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String onExit(@Advice.FieldValue(value = "childTraceContext", typing = Assigner.Typing.DYNAMIC) @Nullable TraceContext childTraceContext) {
if (childTraceContext == null) {
return null;
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java
index 60dcea73ab..0136c0f169 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ScopeManagerInstrumentation.java
@@ -62,7 +62,7 @@ public ActivateInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodEnter(suppress = Throwable.class)
public static void doActivate(@Advice.Argument(value = 0, typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span) {
if (span != null) {
span.activate();
@@ -79,7 +79,7 @@ public CurrentSpanInstrumentation() {
@Nullable
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Object getCurrentSpan() {
if (tracer == null) {
return null;
@@ -98,7 +98,7 @@ public CurrentTraceContextInstrumentation() {
@Nullable
@AssignToReturn
@VisibleForAdvice
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Object getCurrentTraceContext() {
if (tracer == null) {
return null;
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java
index 6c3537e81b..98cce76aa1 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/SpanContextInstrumentation.java
@@ -72,7 +72,7 @@ public BaggageItemsInstrumentation() {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static Iterable> baggageItems(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext) {
if (traceContext != null) {
return doGetBaggage(traceContext);
@@ -98,7 +98,7 @@ public ToTraceIdInstrumentation() {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String toTraceId(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext) {
if (traceContext == null) {
return null;
@@ -115,7 +115,7 @@ public ToSpanIdInstrumentation() {
@Nullable
@AssignToReturn
- @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
+ @Advice.OnMethodExit(suppress = Throwable.class)
public static String toTraceId(@Advice.FieldValue(value = "traceContext", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> traceContext) {
if (traceContext == null) {
return null;
From 42d483914945ed6e26d0c78048ea3a78204eb906 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Sun, 14 Jun 2020 17:34:22 +0200
Subject: [PATCH 16/35] Add docs, rename indyDispatch to indyPlugin
---
.../apm/agent/bci/ElasticApmAgent.java | 57 +-----------
.../agent/bci/ElasticApmInstrumentation.java | 4 +-
.../elastic/apm/agent/bci/IndyBootstrap.java | 6 +-
.../bci/bytebuddy/ErrorLoggingListener.java | 7 --
.../PatchBytecodeVersionTo51Transformer.java | 90 +++++++++++++++++++
.../bci/bytebuddy/postprocessor/AssignTo.java | 11 ++-
.../postprocessor/AssignToArgument.java | 19 +++-
.../postprocessor/AssignToField.java | 9 +-
.../postprocessor/AssignToReturn.java | 10 +++
.../bytebuddy/postprocessor/package-info.java | 32 ++++++-
.../threadlocal/RemoveOnGetThreadLocal.java | 6 ++
.../apm/agent/bci/InstrumentationTest.java | 14 +--
.../AdviceInSubpackageInstrumentation.java | 6 +-
.../apm/agent/jdbc/JdbcInstrumentation.java | 2 +-
.../AbstractServletInstrumentation.java | 2 +-
15 files changed, 190 insertions(+), 85 deletions(-)
create mode 100644 apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/PatchBytecodeVersionTo51Transformer.java
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 593e04f97b..da9f520139 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -29,6 +29,7 @@
import co.elastic.apm.agent.bci.bytebuddy.FailSafeDeclaredMethodsCompiler;
import co.elastic.apm.agent.bci.bytebuddy.MatcherTimer;
import co.elastic.apm.agent.bci.bytebuddy.MinimumClassFileVersionValidator;
+import co.elastic.apm.agent.bci.bytebuddy.PatchBytecodeVersionTo51Transformer;
import co.elastic.apm.agent.bci.bytebuddy.RootPackageCustomLocator;
import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.agent.bci.bytebuddy.SoftlyReferencingTypePoolCache;
@@ -48,27 +49,18 @@
import net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.asm.Advice;
-import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.annotation.AnnotationDescription;
-import net.bytebuddy.description.field.FieldDescription;
-import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
-import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ByteArrayClassLoader;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
-import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaModule;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.configuration.ConfigurationOption;
@@ -107,7 +99,6 @@
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static net.bytebuddy.matcher.ElementMatchers.not;
-import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
public class ElasticApmAgent {
@@ -301,47 +292,7 @@ public boolean matches(TypeDescription typeDescription, ClassLoader classLoader,
}
}
})
- .transform(new AgentBuilder.Transformer() {
- @Override
- public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
- return builder.visit(new AsmVisitorWrapper.AbstractBase() {
- @Override
- public ClassVisitor wrap(TypeDescription typeDescription, ClassVisitor classVisitor, Implementation.Context context,
- TypePool typePool, FieldList fieldList, MethodList> methodList, int writerFlags, int readerFlags) {
- return new ClassVisitor(Opcodes.ASM7, classVisitor) {
- private boolean patchVersion;
-
- @Override
- public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
- if (version < Opcodes.V1_7) {
- patchVersion = true;
- // up-patching the class file verison to version 7 in order to support advice dispatching via invoke dynamic
- version = Opcodes.V1_7;
- }
- super.visit(version, access, name, signature, superName, interfaces);
- }
-
- @Override
- public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
- final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
- if (patchVersion) {
- return new JSRInlinerAdapter(methodVisitor, access, name, descriptor, signature, exceptions);
- } else {
- return methodVisitor;
- }
- }
- };
- }
-
- @Override
- public int mergeWriter(int flags) {
- // class files with version < Java 7 don't require a stack frame map
- // as we're patching the version to at least 7, we have to compute the frames
- return flags | COMPUTE_FRAMES;
- }
- });
- }
- })
+ .transform(new PatchBytecodeVersionTo51Transformer())
.transform(getTransformer(tracer, instrumentation, logger, methodMatcher))
.transform(new AgentBuilder.Transformer() {
@Override
@@ -362,7 +313,7 @@ private static AgentBuilder.Transformer.ForAdvice getTransformer(final ElasticAp
if (offsetMapping != null) {
withCustomMapping = withCustomMapping.bind(offsetMapping);
}
- if (instrumentation.indyDispatch()) {
+ if (instrumentation.indyPlugin()) {
validateAdvice(instrumentation.getAdviceClass().getName());
withCustomMapping = withCustomMapping.bootstrap(IndyBootstrap.getIndyBootstrapMethod());
}
@@ -394,7 +345,7 @@ public boolean matches(MethodDescription target) {
}
/**
- * Validates invariants explained in {@link ElasticApmInstrumentation#indyDispatch()}
+ * Validates invariants explained in {@link ElasticApmInstrumentation#indyPlugin()}
* @param adviceClassName the name of the advice class
*/
private static void validateAdvice(String adviceClassName) {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
index e657bc482c..6129e4e475 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
@@ -205,7 +205,7 @@ public void onTypeMatch(TypeDescription typeDescription, ClassLoader classLoader
* this will call {@link IndyBootstrap#bootstrap} to determine the target {@link java.lang.invoke.ConstantCallSite}.
*
*
- * Things to watch out for when using indy dispatch:
+ * Things to watch out for when using indy plugins:
*
*
* When an advice instruments classes in multiple class loaders, the plugin classes will be loaded form multiple class loaders.
@@ -235,7 +235,7 @@ public void onTypeMatch(TypeDescription typeDescription, ClassLoader classLoader
* and dispatch to the {@linkplain #getAdviceClass() advice} via an {@code INVOKEDYNAMIC} instruction.
* @see IndyBootstrap
*/
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return false;
}
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
index e94815c4f2..d2863a0dd5 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
@@ -45,7 +45,7 @@
import static net.bytebuddy.matcher.ElementMatchers.named;
/**
- * When {@link ElasticApmInstrumentation#indyDispatch()} returns {@code true},
+ * When {@link ElasticApmInstrumentation#indyPlugin()} returns {@code true},
* we instruct Byte Buddy (via {@link Advice.WithCustomMapping#bootstrap(java.lang.reflect.Method)})
* to dispatch {@linkplain Advice.OnMethodEnter#inline() non-inlined advices} via an invokedynamic (indy) instruction.
* The target method is linked to a dynamically created plugin class loader that is specific to an instrumentation plugin
@@ -140,10 +140,10 @@
*
*
* There are some things to watch out for when writing plugins,
- * as explained in {@link ElasticApmInstrumentation#indyDispatch()}
+ * as explained in {@link ElasticApmInstrumentation#indyPlugin()}
*
*
- * @see ElasticApmInstrumentation#indyDispatch()
+ * @see ElasticApmInstrumentation#indyPlugin()
*/
public class IndyBootstrap {
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
index 71a3da1011..0994bd5bbf 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/ErrorLoggingListener.java
@@ -25,17 +25,10 @@
package co.elastic.apm.agent.bci.bytebuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.utility.JavaModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
public class ErrorLoggingListener extends AgentBuilder.Listener.Adapter {
private static final Logger logger = LoggerFactory.getLogger(ErrorLoggingListener.class);
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/PatchBytecodeVersionTo51Transformer.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/PatchBytecodeVersionTo51Transformer.java
new file mode 100644
index 0000000000..54b67b5870
--- /dev/null
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/PatchBytecodeVersionTo51Transformer.java
@@ -0,0 +1,90 @@
+/*-
+ * #%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.bytebuddy;
+
+import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.pool.TypePool;
+import net.bytebuddy.utility.JavaModule;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.JSRInlinerAdapter;
+
+import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
+
+/**
+ * Patches the class file version to 51 (Java 7) in order to support injecting {@code INVOKEDYNAMIC} instructions via
+ * {@link Advice.WithCustomMapping#bootstrap} which is important for {@linkplain ElasticApmInstrumentation#indyPlugin() indy plugins}.
+ */
+public class PatchBytecodeVersionTo51Transformer implements AgentBuilder.Transformer {
+ @Override
+ public DynamicType.Builder> transform(DynamicType.Builder> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
+ return builder.visit(new AsmVisitorWrapper.AbstractBase() {
+ @Override
+ public ClassVisitor wrap(TypeDescription typeDescription, ClassVisitor classVisitor, Implementation.Context context,
+ TypePool typePool, FieldList fieldList, MethodList> methodList, int writerFlags, int readerFlags) {
+ return new ClassVisitor(Opcodes.ASM7, classVisitor) {
+ private boolean patchVersion;
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ if (version < Opcodes.V1_7) {
+ patchVersion = true;
+ //
+ version = Opcodes.V1_7;
+ }
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ final MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (patchVersion) {
+ return new JSRInlinerAdapter(methodVisitor, access, name, descriptor, signature, exceptions);
+ } else {
+ return methodVisitor;
+ }
+ }
+ };
+ }
+
+ @Override
+ public int mergeWriter(int flags) {
+ // class files with version < Java 7 don't require a stack frame map
+ // as we're patching the version to at least 7, we have to compute the frames
+ return flags | COMPUTE_FRAMES;
+ }
+ });
+ }
+}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
index 1470fbddc2..4abad800fa 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignTo.java
@@ -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
@@ -29,9 +29,16 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+/**
+ * Used to assign a single {@code Object[]} return value of an advice to multiple bindings.
+ */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AssignTo {
+ /**
+ * The arguments to assign to
+ * @return
+ */
AssignToArgument[] arguments() default {};
AssignToField[] fields() default {};
AssignToReturn[] returns() default {};
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
index c6b42ba9b5..d6c458f66e 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToArgument.java
@@ -35,9 +35,24 @@
@Target(ElementType.METHOD)
public @interface AssignToArgument {
+ /**
+ * Returns the index of the mapped parameter.
+ *
+ * @return The index of the mapped parameter.
+ */
int value();
- int index() default -1;
-
+ /**
+ * The typing that should be applied when assigning the argument.
+ *
+ * @return The typing to apply upon assignment.
+ */
Assigner.Typing typing() default Assigner.Typing.STATIC;
+
+ /**
+ * Used in combination with {@link AssignTo} to select the index of the returned {@code Object[]} that should be used for the assignment.
+ *
+ * @return the index of the {@code Object[]} that should be used for the assignment.
+ */
+ int index() default -1;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
index 7cbb6e2958..ab22c7fb7e 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToField.java
@@ -42,8 +42,6 @@
*/
String value();
- int index() default -1;
-
/**
* Returns the type that declares the field that should be mapped to the annotated parameter. If this property
* is set to {@code void}, the field is looked up implicitly within the instrumented class's class hierarchy.
@@ -60,4 +58,11 @@
* @return The typing to apply upon assignment.
*/
Assigner.Typing typing() default Assigner.Typing.STATIC;
+
+ /**
+ * Used in combination with {@link AssignTo} to select the index of the returned {@code Object[]} that should be used for the assignment.
+ *
+ * @return the index of the {@code Object[]} that should be used for the assignment.
+ */
+ int index() default -1;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
index 42c26f3e3b..b1d9ab2626 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/AssignToReturn.java
@@ -35,7 +35,17 @@
@Target(ElementType.METHOD)
public @interface AssignToReturn {
+ /**
+ * Determines the typing that is applied when assigning the return value.
+ *
+ * @return The typing to apply when assigning the annotated parameter.
+ */
Assigner.Typing typing() default Assigner.Typing.STATIC;
+ /**
+ * Used in combination with {@link AssignTo} to select the index of the returned {@code Object[]} that should be used for the assignment.
+ *
+ * @return the index of the {@code Object[]} that should be used for the assignment.
+ */
int index() default -1;
}
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java
index 1a873c0d7a..a8c490a24d 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/bytebuddy/postprocessor/package-info.java
@@ -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
@@ -22,6 +22,34 @@
* under the License.
* #L%
*/
+/**
+ * A limitation of non-{@linkplain net.bytebuddy.asm.Advice.OnMethodEnter#inline() inlined advices} is that the {@code readOnly} property
+ * of annotations that bind values to advice method parameters cannot be used.
+ *
+ * Because we make heavy use of non-inlined advices for
+ * {@linkplain co.elastic.apm.agent.bci.ElasticApmInstrumentation#indyPlugin() indy plugins},
+ * this package provides alternative means to bind values:
+ *
+ * {@link co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignTo}:
+ * Substitute of binding multiple values in a single method.
+ * Works by returning an {@code Object[]} from the advice method.
+ *
+ *
+ */
@NonnullApi
package co.elastic.apm.agent.bci.bytebuddy.postprocessor;
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
index 46728d816c..b4fc8af159 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/threadlocal/RemoveOnGetThreadLocal.java
@@ -30,6 +30,12 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+/**
+ * Allows registering a globally shared instance of a {@link DetachedThreadLocal} that allows for removal on get.
+ *
+ * @param
+ * @see co.elastic.apm.agent.util.GlobalVariables
+ */
public class RemoveOnGetThreadLocal extends DetachedThreadLocal {
private static final ConcurrentMap> registry = new ConcurrentHashMap<>();
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index 76edca4bdc..f7a88d2855 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -29,12 +29,12 @@
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToField;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToReturn;
+import co.elastic.apm.agent.bci.subpackage.AdviceInSubpackageInstrumentation;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.configuration.SpyConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import co.elastic.apm.agent.matcher.WildcardMatcher;
-import co.elastic.apm.agent.bci.subpackage.AdviceInSubpackageInstrumentation;
import co.elastic.apm.agent.util.GlobalVariables;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.asm.Advice;
@@ -661,7 +661,7 @@ public Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
@@ -697,7 +697,7 @@ public Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
@@ -733,7 +733,7 @@ public Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
@@ -769,7 +769,7 @@ public Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
@@ -798,7 +798,7 @@ public Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
@@ -825,7 +825,7 @@ public Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java
index 668d97679d..1915668a0a 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/subpackage/AdviceInSubpackageInstrumentation.java
@@ -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
@@ -57,7 +57,7 @@ public Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
diff --git a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
index f97cac4b3d..1761409a1e 100644
--- a/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
+++ b/apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/JdbcInstrumentation.java
@@ -42,7 +42,7 @@ public final Collection getInstrumentationGroupNames() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
diff --git a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AbstractServletInstrumentation.java b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AbstractServletInstrumentation.java
index 29df56ff53..72a40218a3 100644
--- a/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AbstractServletInstrumentation.java
+++ b/apm-agent-plugins/apm-servlet-plugin/src/main/java/co/elastic/apm/agent/servlet/AbstractServletInstrumentation.java
@@ -49,7 +49,7 @@ public ElementMatcher.Junction getClassLoaderMatcher() {
}
@Override
- public boolean indyDispatch() {
+ public boolean indyPlugin() {
return true;
}
}
From 6771a07f6c03b0ee0b39ac46908dce5d3bb15f2b Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Sun, 14 Jun 2020 17:35:01 +0200
Subject: [PATCH 17/35] Fix tests
---
.../ApacheHttpAsyncClientInstrumentation.java | 11 ++++++----
...asticsearchClientAsyncInstrumentation.java | 4 +++-
...asticsearchClientAsyncInstrumentation.java | 4 +++-
.../OkHttp3ClientAsyncInstrumentation.java | 18 +++++++++--------
.../okhttp/OkHttp3ClientInstrumentation.java | 5 +++--
.../OkHttpClientAsyncInstrumentation.java | 20 ++++++++++---------
.../okhttp/OkHttpClientInstrumentation.java | 3 ++-
.../impl/ApmSpanInstrumentation.java | 6 +++---
8 files changed, 42 insertions(+), 29 deletions(-)
diff --git a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
index 921260c3a8..38340d791a 100644
--- a/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
+++ b/apm-agent-plugins/apm-apache-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/ApacheHttpAsyncClientInstrumentation.java
@@ -107,15 +107,18 @@ public static class ApacheHttpAsyncClientAdvice {
@AssignToArgument(index = 0, value = 0),
@AssignToArgument(index = 1, value = 3)
})
+ @Nullable
@Advice.OnMethodEnter(suppress = Throwable.class)
public static Object[] onBeforeExecute(@Advice.Argument(value = 0) HttpAsyncRequestProducer requestProducer,
@Advice.Argument(2) HttpContext context,
@Advice.Argument(value = 3) FutureCallback> futureCallback) {
if (tracer == null || tracer.getActive() == null) {
- return new Object[]{requestProducer, futureCallback, false, null};
+ return null;
}
final AbstractSpan> parent = tracer.getActive();
Span span = parent.createExitSpan();
+ HttpAsyncRequestProducer wrappedProducer = requestProducer;
+ FutureCallback> wrappedFutureCallback = futureCallback;
boolean wrapped = false;
if (span != null) {
span.withType(HttpClientHelper.EXTERNAL_TYPE)
@@ -126,12 +129,12 @@ public static Object[] onBeforeExecute(@Advice.Argument(value = 0) HttpAsyncRequ
asyncHelperManager.getForClassLoaderOfClass(HttpAsyncRequestProducer.class);
TextHeaderSetter headerSetter = headerSetterHelperClassManager.getForClassLoaderOfClass(HttpRequest.class);
if (asyncHelper != null && headerSetter != null) {
- requestProducer = asyncHelper.wrapRequestProducer(requestProducer, span, headerSetter);
- futureCallback = asyncHelper.wrapFutureCallback(futureCallback, context, span);
+ wrappedProducer = asyncHelper.wrapRequestProducer(requestProducer, span, headerSetter);
+ wrappedFutureCallback = asyncHelper.wrapFutureCallback(futureCallback, context, span);
wrapped = true;
}
}
- return new Object[]{requestProducer, futureCallback, wrapped, span};
+ return new Object[]{wrappedProducer, wrappedFutureCallback, wrapped, span};
}
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
index 93d9a1c57b..5e53ecda30 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-5_6/src/main/java/co/elastic/apm/agent/es/restclient/v5_6/ElasticsearchClientAsyncInstrumentation.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.es.restclient.v5_6;
+import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentation;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentationHelper;
@@ -76,7 +77,8 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
public static class ElasticsearchRestClientAsyncAdvice {
- private static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
+ @VisibleForAdvice
+ public static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
@AssignToArgument(5)
@Advice.OnMethodEnter(suppress = Throwable.class)
public static ResponseListener onBeforeExecute(@Advice.Argument(0) String method,
diff --git a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
index a4da26d880..8b65462eb8 100644
--- a/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-es-restclient-plugin/apm-es-restclient-plugin-6_4/src/main/java/co/elastic/apm/agent/es/restclient/v6_4/ElasticsearchClientAsyncInstrumentation.java
@@ -24,6 +24,7 @@
*/
package co.elastic.apm.agent.es.restclient.v6_4;
+import co.elastic.apm.agent.bci.VisibleForAdvice;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToArgument;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentation;
import co.elastic.apm.agent.es.restclient.ElasticsearchRestClientInstrumentationHelper;
@@ -70,7 +71,8 @@ public ElementMatcher super MethodDescription> getMethodMatcher() {
}
public static class ElasticsearchRestClientAsyncAdvice {
- private static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
+ @VisibleForAdvice
+ public static final RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(ElasticsearchRestClientAsyncAdvice.class, "spanTls");
@AssignToArgument(1)
@Advice.OnMethodEnter(suppress = Throwable.class)
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
index 048de64f1a..432cbec5be 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientAsyncInstrumentation.java
@@ -86,22 +86,24 @@ public static class OkHttpClient3ExecuteAdvice {
fields = @AssignToField(index = 0, value = "originalRequest"),
arguments = @AssignToArgument(index = 1, value = 0)
)
+ @Nullable
@Advice.OnMethodEnter(suppress = Throwable.class)
- public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
- @Advice.FieldValue("originalRequest") @Nullable okhttp3.Request originalRequest,
- @Advice.Argument(0) @Nullable Callback callback) {
+ public static Object[] onBeforeEnqueue(final @Advice.Origin Class extends Call> clazz,
+ final @Advice.FieldValue("originalRequest") @Nullable okhttp3.Request originalRequest,
+ final @Advice.Argument(0) @Nullable Callback originalCallback) {
if (tracer == null || tracer.getActive() == null || callbackWrapperCreator == null) {
- return new Object[] {originalRequest, callback, null};
+ return null;
}
final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz);
- if (originalRequest == null || callback == null || wrapperCreator == null) {
- return new Object[] {originalRequest, callback, null};
+ if (originalRequest == null || originalCallback == null || wrapperCreator == null) {
+ return null;
}
final AbstractSpan> parent = tracer.getActive();
okhttp3.Request request = originalRequest;
+ Callback callback = originalCallback;
HttpUrl url = request.url();
Span span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.scheme(),
OkHttpClientHelper.computeHostName(url.host()), url.port());
@@ -112,12 +114,12 @@ public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> claz
if (headerSetter != null) {
Request.Builder builder = originalRequest.newBuilder();
span.propagateTraceContext(builder, headerSetter);
- originalRequest = builder.build();
+ request = builder.build();
}
}
callback = wrapperCreator.wrap(callback, span);
}
- return new Object[] {originalRequest, callback, span};
+ return new Object[] {request, callback, span};
}
@Advice.OnMethodExit(suppress = Throwable.class)
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
index deed505ef7..340e8f3c58 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttp3ClientInstrumentation.java
@@ -59,12 +59,13 @@ public Class> getAdviceClass() {
@VisibleForAdvice
public static class OkHttpClient3ExecuteAdvice {
- private final static RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(OkHttpClient3ExecuteAdvice.class, "spanTls");
+ @VisibleForAdvice
+ public final static RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(OkHttpClient3ExecuteAdvice.class, "spanTls");
@Nullable
@AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
@Advice.OnMethodEnter(suppress = Throwable.class)
- public static Object onBeforeExecute(@Advice.FieldValue("originalRequest") @Nullable Object originalRequest) {
+ public static Object onBeforeExecute(final @Advice.FieldValue("originalRequest") @Nullable Object originalRequest) {
if (tracer == null || tracer.getActive() == null || !(originalRequest instanceof Request)) {
return originalRequest;
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
index 3c7553644b..9c88e26e1d 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientAsyncInstrumentation.java
@@ -84,22 +84,24 @@ public static class OkHttpClient3ExecuteAdvice {
fields = @AssignToField(index = 0, value = "originalRequest"),
arguments = @AssignToArgument(index = 1, value = 0)
)
+ @Nullable
@Advice.OnMethodEnter(suppress = Throwable.class)
- public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> clazz,
- @Advice.FieldValue("originalRequest") @Nullable Request originalRequest,
- @Advice.Argument(0) @Nullable Callback callback) {
+ public static Object[] onBeforeEnqueue(final @Advice.Origin Class extends Call> clazz,
+ final @Advice.FieldValue("originalRequest") @Nullable Request originalRequest,
+ final @Advice.Argument(0) @Nullable Callback originalCallback) {
if (tracer == null || tracer.getActive() == null || callbackWrapperCreator == null) {
- return new Object[] {originalRequest, callback, null};
+ return null;
}
final WrapperCreator wrapperCreator = callbackWrapperCreator.getForClassLoaderOfClass(clazz);
- if (originalRequest == null || callback == null || wrapperCreator == null) {
- return new Object[] {originalRequest, callback, null};
+ if (originalRequest == null || originalCallback == null || wrapperCreator == null) {
+ return null;
}
final AbstractSpan> parent = tracer.getActive();
Request request = originalRequest;
+ Callback callback = originalCallback;
URL url = request.url();
Span span = HttpClientHelper.startHttpClientSpan(parent, request.method(), url.toString(), url.getProtocol(),
OkHttpClientHelper.computeHostName(url.getHost()), url.getPort());
@@ -110,12 +112,12 @@ public static Object[] onBeforeEnqueue(@Advice.Origin Class extends Call> claz
if (headerSetter != null) {
Request.Builder builder = originalRequest.newBuilder();
span.propagateTraceContext(builder, headerSetter);
- originalRequest = builder.build();
+ request = builder.build();
}
}
- callback = wrapperCreator.wrap(callback, span);
+ callback = wrapperCreator.wrap(originalCallback, span);
}
- return new Object[] {originalRequest, callback, span};
+ return new Object[] {request, callback, span};
}
@Advice.OnMethodExit(suppress = Throwable.class)
diff --git a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
index 9a5db14075..d958c25ab9 100644
--- a/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
+++ b/apm-agent-plugins/apm-okhttp-plugin/src/main/java/co/elastic/apm/agent/okhttp/OkHttpClientInstrumentation.java
@@ -58,7 +58,8 @@ public Class> getAdviceClass() {
@VisibleForAdvice
public static class OkHttpClientExecuteAdvice {
- private final static RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(OkHttpClientExecuteAdvice.class, "spanTls");
+ @VisibleForAdvice
+ public final static RemoveOnGetThreadLocal spanTls = RemoveOnGetThreadLocal.get(OkHttpClientExecuteAdvice.class, "spanTls");
@Nullable
@AssignToField(value = "originalRequest", typing = Assigner.Typing.DYNAMIC)
diff --git a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
index 26c5b61e5c..0be3a33f70 100644
--- a/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
+++ b/apm-agent-plugins/apm-opentracing-plugin/src/main/java/co/elastic/apm/agent/opentracing/impl/ApmSpanInstrumentation.java
@@ -112,7 +112,7 @@ public SetOperationName() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void setOperationName(@Advice.FieldValue(value = "dispatcher", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
@Advice.Argument(0) @Nullable String operationName) {
if (span != null) {
@@ -129,7 +129,7 @@ public LogInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void log(@Advice.FieldValue(value = "dispatcher", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
@Advice.Argument(0) long epochTimestampMicros,
@Advice.Argument(1) Map fields) {
@@ -158,7 +158,7 @@ public TagInstrumentation() {
}
@VisibleForAdvice
- @Advice.OnMethodEnter(suppress = Throwable.class)
+ @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static void handleTag(@Advice.FieldValue(value = "dispatcher", typing = Assigner.Typing.DYNAMIC) @Nullable AbstractSpan> span,
@Advice.Argument(0) String key,
@Advice.Argument(1) @Nullable Object value) {
From 87f324f995072547d6069c89e2b9ae6968cf1909 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Mon, 15 Jun 2020 14:00:03 +0200
Subject: [PATCH 18/35] Update Byte Buddy
---
.../elastic/apm/agent/bci/IndyBootstrap.java | 9 ++++-----
.../bootstrap/IndyBootstrapDispatcher.clazz | Bin 3401 -> 3388 bytes
.../bootstrap/IndyBootstrapDispatcher.java | 13 +++++++------
.../apm/agent/bci/InstrumentationTest.java | 17 ++++++-----------
pom.xml | 2 +-
5 files changed, 18 insertions(+), 23 deletions(-)
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
index d2863a0dd5..a1449f3299 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java
@@ -172,9 +172,9 @@ public static Method getIndyBootstrapMethod() {
indyBootstrapClass
.getField("bootstrap")
.set(null, IndyBootstrap.class.getMethod("bootstrap", MethodHandles.Lookup.class, String.class, MethodType.class,
- String.class, Class.class, MethodHandle.class, String.class, int.class));
+ String.class, int.class, Class.class, String.class, Object[].class));
return indyBootstrapMethod = indyBootstrapClass.getMethod("bootstrap", MethodHandles.Lookup.class, String.class,
- MethodType.class, String.class, Class.class, MethodHandle.class, String.class, int.class);
+ MethodType.class, String.class, int.class, Class.class, String.class, Object[].class);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -218,15 +218,14 @@ private static Class> initIndyBootstrap() throws Exception {
* Exceptions and {@code null} return values are handled by {@code java.lang.IndyBootstrapDispatcher#bootstrap}.
*
*/
- @Nullable
public static ConstantCallSite bootstrap(MethodHandles.Lookup lookup,
String adviceMethodName,
MethodType adviceMethodType,
String adviceClassName,
+ int enter,
Class> instrumentedType,
- MethodHandle instrumentedMethod,
String instrumentedMethodName,
- int enter) throws Exception {
+ Object... args) throws Exception {
Class> adviceClass = Class.forName(adviceClassName);
String packageName = adviceClass.getPackage().getName();
List pluginClasses = classesByPackage.get(packageName);
diff --git a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.clazz b/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.clazz
index 5404e5696060da6ebb124c7e12be2874c1eb917e..548a2134101bc855be31e1da8a3b7aaf2f72487d 100644
GIT binary patch
delta 749
zcmZ8fTTc^V5ItYJyVG615P|_yC;}QQiE@dc0tGD>(F%xKL=hAUtmV4YUQk4>cSR}2
zd@sKFVB)(WCPs-S#uxn+zWNi4MxAYZ;$?Q`>^WyH;k7{x^kmoVmrahDQvu?fPYg#(
zg-y8ZL&SXl-
zT8D_JHxy7gjTBL(`c9+*o?jKZ?WFCbfQSeb%{ZxaGPB@9Q-jM1TeW-3%e7cC&Su+2
z`etKGvHJO#6R#hbn6)RqmEP2{ger+zTqRtSSVo;h1$IbW#|q(w#7*2HWF>Cnj>KKulemuu
z5)bi6<@6fku|xw_)dxN3dxEDDYsje|dbPGLuv=AY+f~>s+-Nj9HLoq!IAu=zF4_X5PJ~9F8=wo=)qnb$3FC&7e_(p&KF0CUAZFTH_8|+{*59+QO{4dJjP-Vk8UEO*5yt;%*{@y
ex6%o^wgk@*>rAHf=81i%;im<`*EY)E@Baq{wU5{U
delta 795
zcmaKq$xjqP6vlrI-CaFR9Y(e|M8PFN+`ug?j*0>b$f7ccfC`KgjRm<*ds&&ikRym-PGGfFSCU%LV^(
zQ84Jx=qFS=YO2oaPaI^aE7)lCEhrGg`Zx=gGWG-zN5?W%*Ymnjm|=PnZz1RH*8%cHtD%dezS&B;wx
jt$wUYy_8<5Z!5^^(scnXY0e0bQ>ITBL+@rUTL}FIg;J!S
diff --git a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.java b/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.java
index aed1a54a91..dc0c624039 100644
--- a/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.java
+++ b/apm-agent-core/src/main/resources/bootstrap/IndyBootstrapDispatcher.java
@@ -8,14 +8,14 @@
import java.lang.reflect.Array;
import java.lang.reflect.Method;
-public class IndyBootstrap {
+public class IndyBootstrapDispatcher {
public static Method bootstrap;
private static final MethodHandle VOID_NOOP;
static {
try {
- VOID_NOOP = MethodHandles.lookup().findStatic(IndyBootstrap.class, "voidNoop", MethodType.methodType(void.class));
+ VOID_NOOP = MethodHandles.lookup().findStatic(IndyBootstrapDispatcher.class, "voidNoop", MethodType.methodType(void.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -25,10 +25,10 @@ public static CallSite bootstrap(MethodHandles.Lookup lookup,
String adviceMethodName,
MethodType adviceMethodType,
String adviceClassName,
+ int enter,
Class> instrumentedType,
- MethodHandle instrumentedMethod,
String instrumentedMethodName,
- int enter) {
+ Object... args) {
CallSite callSite = null;
if (bootstrap != null) {
try {
@@ -36,9 +36,10 @@ public static CallSite bootstrap(MethodHandles.Lookup lookup,
adviceMethodName,
adviceMethodType,
adviceClassName,
+ enter,
instrumentedType,
- instrumentedMethod,
- instrumentedMethodName, enter);
+ instrumentedMethodName,
+ args);
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
index f7a88d2855..894d700179 100644
--- a/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/bci/InstrumentationTest.java
@@ -49,7 +49,6 @@
import org.apache.commons.pool2.impl.CallStackUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.slf4j.event.SubstituteLoggingEvent;
import org.stagemonitor.configuration.ConfigurationRegistry;
@@ -283,7 +282,6 @@ String exceptionPlease() {
}
@Test
- @Disabled
void testPatchClassFileVersionJava6ToJava7() {
// loading classes compiled with bytecode level 50 (Java 6)
assertThat(StringUtils.startsWithIgnoreCase("APM", "apm")).isTrue();
@@ -303,7 +301,6 @@ void testPatchClassFileVersionJava6ToJava7() {
}
@Test
- @Disabled
void testPatchClassFileVersionJava5ToJava7() {
// loading classes compiled with bytecode level 49 (Java 6)
new org.slf4j.event.SubstituteLoggingEvent();
@@ -323,7 +320,6 @@ void testPatchClassFileVersionJava5ToJava7() {
}
@Test
- @Disabled
void testPatchClassFileVersionJava5ToJava7CommonsMath() {
org.apache.commons.math3.stat.StatUtils.max(new double[]{3.14});
@@ -342,7 +338,6 @@ void testPatchClassFileVersionJava5ToJava7CommonsMath() {
}
@Test
- @Disabled
void testPrivateConstructorJava7() {
org.apache.commons.pool2.impl.CallStackUtils.newCallStack("", false, false);
@@ -668,8 +663,8 @@ public boolean indyPlugin() {
public static class LoggerFactoryInstrumentation extends ElasticApmInstrumentation {
- public static AtomicInteger enterCount = GlobalVariables.get(CommonsLangInstrumentation.class, "enterCount", new AtomicInteger());
- public static AtomicInteger exitCount = GlobalVariables.get(CommonsLangInstrumentation.class, "exitCount", new AtomicInteger());
+ public static AtomicInteger enterCount = GlobalVariables.get(LoggerFactoryInstrumentation.class, "enterCount", new AtomicInteger());
+ public static AtomicInteger exitCount = GlobalVariables.get(LoggerFactoryInstrumentation.class, "exitCount", new AtomicInteger());
@Advice.OnMethodEnter(inline = false)
public static void onEnter() {
@@ -704,8 +699,8 @@ public boolean indyPlugin() {
public static class StatUtilsInstrumentation extends ElasticApmInstrumentation {
- public static AtomicInteger enterCount = GlobalVariables.get(CommonsLangInstrumentation.class, "enterCount", new AtomicInteger());
- public static AtomicInteger exitCount = GlobalVariables.get(CommonsLangInstrumentation.class, "exitCount", new AtomicInteger());
+ public static AtomicInteger enterCount = GlobalVariables.get(StatUtilsInstrumentation.class, "enterCount", new AtomicInteger());
+ public static AtomicInteger exitCount = GlobalVariables.get(StatUtilsInstrumentation.class, "exitCount", new AtomicInteger());
@Advice.OnMethodEnter(inline = false)
public static void onEnter() {
@@ -740,8 +735,8 @@ public boolean indyPlugin() {
public static class CallStackUtilsInstrumentation extends ElasticApmInstrumentation {
- public static AtomicInteger enterCount = GlobalVariables.get(CommonsLangInstrumentation.class, "enterCount", new AtomicInteger());
- public static AtomicInteger exitCount = GlobalVariables.get(CommonsLangInstrumentation.class, "exitCount", new AtomicInteger());
+ public static AtomicInteger enterCount = GlobalVariables.get(CallStackUtilsInstrumentation.class, "enterCount", new AtomicInteger());
+ public static AtomicInteger exitCount = GlobalVariables.get(CallStackUtilsInstrumentation.class, "exitCount", new AtomicInteger());
@Advice.OnMethodEnter(inline = false)
public static void onEnter() {
diff --git a/pom.xml b/pom.xml
index 1eb4372f98..8105044138 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,7 +96,7 @@
5.0.15.RELEASE9.4.11.v201806050.1.19
- 1.10.11
+ 1.10.12-SNAPSHOT1.17
From 8553bb612b6de200a8cfbeb1a0834ea630394262 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Tue, 16 Jun 2020 11:23:02 +0200
Subject: [PATCH 19/35] Use MutableInt instead of AtomicInteger for CallDepth
---
.../co/elastic/apm/agent/util/CallDepth.java | 44 +++++++++++++++----
.../elastic/apm/agent/util/CallDepthTest.java | 36 +++++++++++++++
2 files changed, 71 insertions(+), 9 deletions(-)
create mode 100644 apm-agent-core/src/test/java/co/elastic/apm/agent/util/CallDepthTest.java
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
index fc311d89a3..ac5c6db385 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/util/CallDepth.java
@@ -28,18 +28,24 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* A utility that makes it easy to detect nested method calls.
*/
public class CallDepth {
private static final ConcurrentMap registry = new ConcurrentHashMap<>();
- private final ThreadLocal callDepthPerThread = new ThreadLocal();
+ private final ThreadLocal callDepthPerThread = new ThreadLocal();
private CallDepth() {
}
+ /**
+ * Returns or creates a globally shared call depth instance, based on the advice's class name.
+ *
+ * @param adviceClass the class of the advice the call depth is used in.
+ * @return a globally shared call depth instance, based on the advice's class name.
+ * @see GlobalVariables
+ */
public static CallDepth get(Class> adviceClass) {
// we want to return the same CallDepth instance even if the advice class has been loaded from different class loaders
String key = adviceClass.getName();
@@ -51,6 +57,10 @@ public static CallDepth get(Class> adviceClass) {
return callDepth;
}
+ static void clearRegistry() {
+ registry.clear();
+ }
+
/**
* Gets and increments the call depth counter.
* Returns {@code 0} if this is the outer-most (non-nested) invocation.
@@ -58,12 +68,7 @@ public static CallDepth get(Class> adviceClass) {
* @return the call depth before it has been incremented
*/
public int increment() {
- AtomicInteger callDepthForCurrentThread = callDepthPerThread.get();
- if (callDepthForCurrentThread == null) {
- callDepthForCurrentThread = new AtomicInteger();
- callDepthPerThread.set(callDepthForCurrentThread);
- }
- return callDepthForCurrentThread.getAndIncrement();
+ return get().getAndIncrement();
}
/**
@@ -87,7 +92,7 @@ public boolean isNestedCallAndIncrement() {
* @return the call depth after it has been incremented
*/
public int decrement() {
- int depth = callDepthPerThread.get().decrementAndGet();
+ int depth = get().decrementAndGet();
assert depth >= 0;
return depth;
}
@@ -105,4 +110,25 @@ public int decrement() {
public boolean isNestedCallAndDecrement() {
return decrement() != 0;
}
+
+ private MutableInt get() {
+ MutableInt callDepthForCurrentThread = callDepthPerThread.get();
+ if (callDepthForCurrentThread == null) {
+ callDepthForCurrentThread = new MutableInt();
+ callDepthPerThread.set(callDepthForCurrentThread);
+ }
+ return callDepthForCurrentThread;
+ }
+
+ private static class MutableInt {
+ private int i;
+
+ public int getAndIncrement() {
+ return i++;
+ }
+
+ public int decrementAndGet() {
+ return --i;
+ }
+ }
}
diff --git a/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CallDepthTest.java b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CallDepthTest.java
new file mode 100644
index 0000000000..2683c8f0a7
--- /dev/null
+++ b/apm-agent-core/src/test/java/co/elastic/apm/agent/util/CallDepthTest.java
@@ -0,0 +1,36 @@
+package co.elastic.apm.agent.util;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class CallDepthTest {
+
+ private CallDepth callDepth;
+
+ @BeforeEach
+ void setUp() {
+ callDepth = CallDepth.get(CallDepthTest.class);
+ }
+
+ @AfterEach
+ void tearDown() {
+ CallDepth.clearRegistry();
+ }
+
+ @Test
+ void testDetectNestedCalls() {
+ assertThat(callDepth.isNestedCallAndIncrement()).isFalse();
+ assertThat(callDepth.isNestedCallAndIncrement()).isTrue();
+ assertThat(callDepth.isNestedCallAndDecrement()).isTrue();
+ assertThat(callDepth.isNestedCallAndDecrement()).isFalse();
+ }
+
+ @Test
+ void testNegativeCount() {
+ assertThatThrownBy(() -> callDepth.decrement()).isInstanceOf(AssertionError.class);
+ }
+}
From 6abbf01aa4b5e1c68dec3679822679b6b54676c3 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Thu, 18 Jun 2020 12:34:47 +0200
Subject: [PATCH 20/35] Make concurrent, process, and HttpUrlConnection indy
plugins
- Fix matcher for ForkJoinPool
- Add support for parallel streams
- Simplify testing of bootstrap instrumentations
- Remove java.* from the default exclusions
- Disallow agent types in advice signature
- Enable previously disabled async Dubbo tests
---
.../apm/agent/bci/ElasticApmAgent.java | 59 ++-
.../agent/bci/ElasticApmInstrumentation.java | 24 +-
.../elastic/apm/agent/bci/IndyBootstrap.java | 6 +-
.../TraceMethodInstrumentation.java | 5 +
.../bootstrap/IndyBootstrapDispatcher.clazz | Bin 3388 -> 3403 bytes
.../bootstrap/IndyBootstrapDispatcher.java | 2 +-
.../elastic/apm/agent/util/CallDepthTest.java | 24 ++
.../src/test/resources/elasticapm.properties | 2 -
.../dubbo/ApacheDubboInstrumentationTest.java | 7 -
.../agent/dubbo/ExecutorServiceWrapper.java | 116 ------
.../dubbo/api/impl/DubboTestApiImpl.java | 3 +-
.../concurrent/ExecutorInstrumentation.java | 110 +++---
.../ForkJoinTaskInstrumentation.java | 76 ++++
.../apm/agent/concurrent/JavaConcurrent.java | 21 +-
...leCallableForkJoinTaskInstrumentation.java | 20 +-
.../apm/agent/concurrent/package-info.java | 8 +-
...ic.apm.agent.bci.ElasticApmInstrumentation | 1 +
.../AsyncTraceMethodInstrumentationTest.java | 2 +-
.../concurrent/ExcludedExecutorClassTest.java | 6 +-
.../ExecutorServiceDoubleWrappingTest.java | 3 +-
.../ExecutorServiceInstrumentationTest.java | 45 +--
.../concurrent/ExecutorServiceWrapper.java | 120 ------
.../agent/concurrent/ForkJoinPoolTest.java | 70 +++-
.../InstrumentableForkJoinPool.java | 114 ------
.../RunnableWrapperExecutorService.java | 85 ----
.../agent/concurrent/ScopeManagementTest.java | 8 +-
.../agent/jdbc/StatementInstrumentation.java | 38 +-
.../process/BaseProcessInstrumentation.java | 4 +
.../CommonsExecAsyncInstrumentation.java | 17 +-
.../process/ProcessExitInstrumentation.java | 12 +-
.../apm/agent/process/ProcessHelper.java | 10 +-
.../process/ProcessStartInstrumentation.java | 2 +-
.../HttpUrlConnectionInstrumentation.java | 23 +-
.../SSLContextInstrumentation.java | 7 +-
.../HttpUrlConnectionInstrumentationTest.java | 365 +-----------------
35 files changed, 409 insertions(+), 1006 deletions(-)
delete mode 100644 apm-agent-plugins/apm-dubbo-plugin/src/test/java/co/elastic/apm/agent/dubbo/ExecutorServiceWrapper.java
create mode 100644 apm-agent-plugins/apm-java-concurrent-plugin/src/main/java/co/elastic/apm/agent/concurrent/ForkJoinTaskInstrumentation.java
delete mode 100644 apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/ExecutorServiceWrapper.java
delete mode 100644 apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/InstrumentableForkJoinPool.java
delete mode 100644 apm-agent-plugins/apm-java-concurrent-plugin/src/test/java/co/elastic/apm/agent/concurrent/RunnableWrapperExecutorService.java
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
index 6dc25694bc..ca22255c9b 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmAgent.java
@@ -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;
@@ -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;
@@ -350,19 +349,15 @@ 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()));
@@ -370,12 +365,30 @@ private static void validateAdviceIsInlined(String adviceClassName) {
}
}
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) {
@@ -493,32 +506,6 @@ public Iterable extends List>> onError(int index, List> 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."))
diff --git a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
index 6129e4e475..e09684e82e 100644
--- a/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
+++ b/apm-agent-core/src/main/java/co/elastic/apm/agent/bci/ElasticApmInstrumentation.java
@@ -206,8 +206,25 @@ public void onTypeMatch(TypeDescription typeDescription, ClassLoader classLoader
*
*
* Things to watch out for when using indy plugins:
+ *
*
*
+ * 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.
+ *
+ *
+ * Both the return type and the arguments of advice methods must no contain types from the agent.
+ * 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.
+ * 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.
+ *
+ *
* 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.
@@ -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.
*
- *
- * 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.
- *