Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate trace_methods to indy plugin #2094

Merged
merged 4 commits into from Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.asciidoc
Expand Up @@ -44,7 +44,7 @@ user to configure an arbitrary agent version that will be downloaded from maven

[float]
===== Refactorings
* Migrate Dubbo plugin to indy dispatcher {pull}2087[#2087]
* Migrate multiple plugins to indy dispatcher {pull}2087[#2087], {pull}2094[#2094]

[[release-notes-1.x]]
=== Java Agent version 1.x
Expand Down
Expand Up @@ -29,19 +29,19 @@
import co.elastic.apm.agent.bci.bytebuddy.SoftlyReferencingTypePoolCache;
import co.elastic.apm.agent.bci.bytebuddy.postprocessor.AssignToPostProcessorFactory;
import co.elastic.apm.agent.bci.classloading.ExternalPluginClassLoader;
import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentation;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import co.elastic.apm.agent.impl.GlobalTracer;
import co.elastic.apm.agent.premain.AgentMain;
import co.elastic.apm.agent.premain.ThreadUtils;
import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
import co.elastic.apm.agent.sdk.weakmap.WeakMapSupplier;
import co.elastic.apm.agent.tracemethods.MethodMatcher;
import co.elastic.apm.agent.tracemethods.TraceMethodInstrumentation;
import co.elastic.apm.agent.util.DependencyInjectingServiceLoader;
import co.elastic.apm.agent.util.ExecutorUtils;
import co.elastic.apm.agent.util.ObjectUtils;
import co.elastic.apm.agent.premain.ThreadUtils;
import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
Expand Down
Expand Up @@ -19,8 +19,6 @@
package co.elastic.apm.agent.configuration;

import co.elastic.apm.agent.bci.ElasticApmAgent;
import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.bci.methodmatching.configuration.MethodMatcherValueConverter;
import co.elastic.apm.agent.configuration.converter.ListValueConverter;
import co.elastic.apm.agent.configuration.converter.RoundedDoubleConverter;
import co.elastic.apm.agent.configuration.converter.TimeDuration;
Expand All @@ -29,6 +27,8 @@
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.matcher.WildcardMatcher;
import co.elastic.apm.agent.matcher.WildcardMatcherValueConverter;
import co.elastic.apm.agent.tracemethods.MethodMatcher;
import co.elastic.apm.agent.tracemethods.configuration.MethodMatcherValueConverter;
import org.stagemonitor.configuration.ConfigurationOption;
import org.stagemonitor.configuration.ConfigurationOptionProvider;
import org.stagemonitor.configuration.converter.MapValueConverter;
Expand Down
Expand Up @@ -16,10 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.bci.methodmatching;
package co.elastic.apm.agent.tracemethods;

import co.elastic.apm.agent.matcher.AnnotationMatcher;
import co.elastic.apm.agent.matcher.WildcardMatcher;
import co.elastic.apm.agent.sdk.state.GlobalState;
import org.stagemonitor.util.StringUtils;

import javax.annotation.Nullable;
Expand All @@ -32,6 +33,7 @@
import static co.elastic.apm.agent.matcher.AnnotationMatcher.annotationMatcher;
import static co.elastic.apm.agent.matcher.WildcardMatcher.caseSensitiveMatcher;

@GlobalState
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[question] If I understand it correctly, we only need global state here as it relates to configuration like MethodMatcherValueConverter, and thus needs to be loaded in the main agent classloader and not in the plugin classloader, is that correct ? If so, maybe a small comment could make that slightly more obvious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Agree on adding an explanatory comment.

public class MethodMatcher {

private static final String MODIFIER = "(?<modifier>public|private|protected|\\*)";
Expand Down
Expand Up @@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.bci.methodmatching;
package co.elastic.apm.agent.tracemethods;

import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
import co.elastic.apm.agent.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.GlobalTracer;
import co.elastic.apm.agent.impl.transaction.AbstractSpan;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.matcher.WildcardMatcher;
import co.elastic.apm.agent.sdk.ElasticApmInstrumentation;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
Expand All @@ -45,55 +46,18 @@
import static net.bytebuddy.matcher.ElementMatchers.isNative;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

public class TraceMethodInstrumentation extends TracerAwareInstrumentation {
public class TraceMethodInstrumentation extends ElasticApmInstrumentation {

public static long traceMethodThresholdMicros;

protected final MethodMatcher methodMatcher;
private final MethodMatcher methodMatcher;
private final CoreConfiguration config;

public TraceMethodInstrumentation(ElasticApmTracer tracer, MethodMatcher methodMatcher) {
this.methodMatcher = methodMatcher;
config = tracer.getConfig(CoreConfiguration.class);
traceMethodThresholdMicros = config.getTraceMethodsDurationThreshold().getMillis() * 1000;
}

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onMethodEnter(@Advice.Origin Class<?> clazz,
@SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature String signature,
@Advice.Local("span") AbstractSpan<?> span) {
final AbstractSpan<?> parent = tracer.getActive();
if (parent == null) {
span = tracer.startRootTransaction(clazz.getClassLoader());
if (span != null) {
span.withName(signature).activate();
}
} else if (parent.isSampled()) {
span = parent.createSpan()
.withName(signature)
.activate();
}
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
public static void onMethodExit(@Advice.Local("span") @Nullable AbstractSpan<?> span,
@Advice.Thrown @Nullable Throwable t) {
if (span != null) {
span.captureException(t);
final long endTime = span.getTraceContext().getClock().getEpochMicros();
if (span instanceof Span) {
long durationMicros = endTime - span.getTimestamp();
if (traceMethodThresholdMicros > 0 && durationMicros < traceMethodThresholdMicros && t == null) {
span.requestDiscarding();
}
}
span.deactivate().end(endTime);
}
}

@Override
Expand Down Expand Up @@ -153,7 +117,55 @@ public Collection<String> getInstrumentationGroupNames() {
}

@Override
public boolean indyPlugin() {
return false;
public String getAdviceClassName() {
return getClass().getName() + "$TraceMethodAdvice";
}

public static class TraceMethodAdvice {

private static final ElasticApmTracer tracer = GlobalTracer.requireTracerImpl();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] moving tracer init to the start of the static block makes the dependency to retrieve configuration obvious.

private static final long traceMethodThresholdMicros;

static {
CoreConfiguration config = tracer.getConfig(CoreConfiguration.class);
traceMethodThresholdMicros = config.getTraceMethodsDurationThreshold().getMillis() * 1000;
}

@Nullable
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static Object onMethodEnter(@Advice.Origin Class<?> clazz,
@SimpleMethodSignatureOffsetMappingFactory.SimpleMethodSignature String signature) {
AbstractSpan<?> span = null;
final AbstractSpan<?> parent = tracer.getActive();
if (parent == null) {
span = tracer.startRootTransaction(clazz.getClassLoader());
if (span != null) {
span.withName(signature).activate();
}
} else if (parent.isSampled()) {
span = parent.createSpan()
.withName(signature)
.activate();
}
return span;
}

@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class, inline = false)
public static void onMethodExit(@Advice.Enter @Nullable Object spanObj,
@Advice.Thrown @Nullable Throwable t) {
AbstractSpan<?> span = (AbstractSpan<?>) spanObj;
if (span != null) {
span.captureException(t);
final long endTime = span.getTraceContext().getClock().getEpochMicros();
if (span instanceof Span) {
long durationMicros = endTime - span.getTimestamp();
if (traceMethodThresholdMicros > 0 && durationMicros < traceMethodThresholdMicros && t == null) {
span.requestDiscarding();
}
}
span.deactivate().end(endTime);
}
}
}

}
Expand Up @@ -16,15 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.bci.methodmatching.configuration;
package co.elastic.apm.agent.tracemethods.configuration;

import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.configuration.converter.ListValueConverter;

import co.elastic.apm.agent.sdk.state.GlobalState;
import co.elastic.apm.agent.tracemethods.MethodMatcher;
import org.stagemonitor.configuration.converter.ValueConverter;

import static co.elastic.apm.agent.configuration.converter.ListValueConverter.COMMA_OUT_OF_BRACKETS;

@GlobalState
public enum MethodMatcherValueConverter implements ValueConverter<MethodMatcher> {
INSTANCE;

Expand Down
Expand Up @@ -17,6 +17,6 @@
* under the License.
*/
@NonnullApi
package co.elastic.apm.agent.bci.methodmatching.configuration;
package co.elastic.apm.agent.tracemethods.configuration;

import co.elastic.apm.agent.sdk.NonnullApi;
Expand Up @@ -17,6 +17,6 @@
* under the License.
*/
@NonnullApi
package co.elastic.apm.agent.bci.methodmatching;
package co.elastic.apm.agent.tracemethods;

import co.elastic.apm.agent.sdk.NonnullApi;
Expand Up @@ -16,12 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.bci.methodmatching;
package co.elastic.apm.agent.tracemethods;

import co.elastic.apm.agent.MockReporter;
import co.elastic.apm.agent.configuration.SpyConfiguration;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.impl.ElasticApmTracerBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import org.junit.jupiter.api.BeforeAll;
Expand Down
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.bci.methodmatching;
package co.elastic.apm.agent.tracemethods;

import org.junit.jupiter.api.Test;

Expand Down
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.bci.methodmatching;
package co.elastic.apm.agent.tracemethods;

import co.elastic.apm.agent.MockReporter;
import co.elastic.apm.agent.MockTracer;
Expand Down Expand Up @@ -65,12 +65,12 @@ void setUp(TestInfo testInfo) {
ConfigurationRegistry config = mockInstrumentationSetup.getConfig();
coreConfiguration = config.getConfig(CoreConfiguration.class);
when(coreConfiguration.getTraceMethods()).thenReturn(Arrays.asList(
MethodMatcher.of("private co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentationTest$TestClass#traceMe*()"),
MethodMatcher.of("private co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentationTest$TestDiscardableMethods#*"),
MethodMatcher.of("private co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentationTest$TestErrorCapture#*"),
MethodMatcher.of("co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentationTest$TestExcludeConstructor#*"),
MethodMatcher.of("public @co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentationTest$CustomAnnotation co.elastic.apm.agent.bci.methodmatching*"),
MethodMatcher.of("public @@co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentationTest$Meta* co.elastic.apm.agent.bci.methodmatching*"))
MethodMatcher.of("private co.elastic.apm.agent.tracemethods.TraceMethodInstrumentationTest$TestClass#traceMe*()"),
MethodMatcher.of("private co.elastic.apm.agent.tracemethods.TraceMethodInstrumentationTest$TestDiscardableMethods#*"),
MethodMatcher.of("private co.elastic.apm.agent.tracemethods.TraceMethodInstrumentationTest$TestErrorCapture#*"),
MethodMatcher.of("co.elastic.apm.agent.tracemethods.TraceMethodInstrumentationTest$TestExcludeConstructor#*"),
MethodMatcher.of("public @co.elastic.apm.agent.tracemethods.TraceMethodInstrumentationTest$CustomAnnotation co.elastic.apm.agent.tracemethods*"),
MethodMatcher.of("public @@co.elastic.apm.agent.tracemethods.TraceMethodInstrumentationTest$Meta* co.elastic.apm.agent.tracemethods*"))
);
when(coreConfiguration.getMethodsExcludedFromInstrumentation()).thenReturn(Arrays.asList(
WildcardMatcher.valueOf("*exclude*"),
Expand Down Expand Up @@ -108,7 +108,7 @@ void testTraceMethod() {
@Test
void testReInitTraceMethod() throws Exception {
when(coreConfiguration.getTraceMethods()).thenReturn(
List.of(MethodMatcher.of("private co.elastic.apm.agent.bci.methodmatching.TraceMethodInstrumentationTest$TestClass#traceMe()")));
List.of(MethodMatcher.of("private co.elastic.apm.agent.tracemethods.TraceMethodInstrumentationTest$TestClass#traceMe()")));
ElasticApmAgent.reInitInstrumentation().get();
TestClass.traceMe();
assertThat(reporter.getTransactions()).hasSize(1);
Expand Down
Expand Up @@ -16,17 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.bci.methodmatching.configuration;
package co.elastic.apm.agent.tracemethods.configuration;

import co.elastic.apm.agent.configuration.converter.ListValueConverter;
import co.elastic.apm.agent.tracemethods.MethodMatcher;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.configuration.converter.ListValueConverter;

import static org.assertj.core.api.Assertions.assertThat;

class MethodMatcherValueConverterTest {
Expand Down
Expand Up @@ -21,10 +21,10 @@
import co.elastic.apm.agent.MockReporter;
import co.elastic.apm.agent.MockTracer;
import co.elastic.apm.agent.bci.ElasticApmAgent;
import co.elastic.apm.agent.bci.methodmatching.MethodMatcher;
import co.elastic.apm.agent.configuration.CoreConfiguration;
import co.elastic.apm.agent.configuration.converter.TimeDuration;
import co.elastic.apm.agent.impl.ElasticApmTracer;
import co.elastic.apm.agent.tracemethods.MethodMatcher;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
Expand Down