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

Binary execution support #903

Merged
merged 59 commits into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
107a604
Support commons-exec synchronous api
theobisproject Aug 29, 2019
2e6b42e
Fix ClassNotFound exception when attaching agent to wildfly
theobisproject Oct 11, 2019
dab2b98
Add draft implementation for commons-exec async, process builder and …
theobisproject Oct 25, 2019
948e3dc
Use the ExecuteHelper in the Java Runtime instrumentation
theobisproject Oct 25, 2019
e8f0a6f
Working commons-exec async instrumentation
theobisproject Oct 25, 2019
c2a97f7
Fix nullpointer
theobisproject Oct 25, 2019
165d81f
Remove redundant checks which ExecuteHelper.createAndActivateSpan alr…
theobisproject Oct 25, 2019
2535e68
Class of EndProcessInstrumentation will be loaded by the bootstrap cl…
theobisproject Nov 6, 2019
3e949c4
add few notes (wip commit)
SylvainJuge Nov 14, 2019
6dd2b0b
allow java.lang.Process{,Builder} instrumentation
SylvainJuge Nov 25, 2019
993fde8
allow java.lang.*Process* instrumentation
SylvainJuge Nov 27, 2019
b670401
add command test servlet
SylvainJuge Nov 27, 2019
4471503
use simple java.lang.Process instrumentation
SylvainJuge Nov 27, 2019
424ee86
deprecate initial implementation
SylvainJuge Nov 27, 2019
179292b
post quick review changes
SylvainJuge Nov 28, 2019
66e03d4
add ProcessHelper test
SylvainJuge Nov 28, 2019
94bedfd
add simple integration test
SylvainJuge Nov 29, 2019
5caf2c5
mark plugin as incubating & deprecate old impl
SylvainJuge Nov 29, 2019
85355af
remove initial implementation
SylvainJuge Nov 29, 2019
45ea4fd
rename package for consistency
SylvainJuge Nov 29, 2019
6bf30bd
fix minor leak
SylvainJuge Nov 29, 2019
408e858
make it java7 compliant
SylvainJuge Nov 29, 2019
a23a0c0
fix licence headers
SylvainJuge Nov 29, 2019
c4b6087
update doc with 'process' incubating feature
SylvainJuge Nov 29, 2019
b7299c4
fix process start instrument ProcessBuilder
SylvainJuge Nov 29, 2019
3d60c16
enable all instrumentations for integration tests
SylvainJuge Nov 29, 2019
2759090
make test app java7 compatible
SylvainJuge Nov 29, 2019
ac0a679
improve instrumentation test error msg
SylvainJuge Dec 3, 2019
52a357f
remove useless annotation
SylvainJuge Dec 3, 2019
36fb020
fix active span leak + shorter naming
SylvainJuge Dec 3, 2019
b96124c
test for short span naming & detect active span leaks
SylvainJuge Dec 3, 2019
3da3bed
code cleanup
SylvainJuge Dec 3, 2019
16e38f2
rename plugin 'cmd' -> 'process'
SylvainJuge Dec 4, 2019
5f1d800
add Readme for plugin
SylvainJuge Dec 4, 2019
2dae898
fix plugin renamed maven module
SylvainJuge Dec 4, 2019
c78abc4
properly rename artifact name
SylvainJuge Dec 4, 2019
ec3c2fc
post PR review changes
SylvainJuge Dec 5, 2019
7c37322
add support for commons-exec async process
SylvainJuge Dec 5, 2019
ba79491
minor fixes + update readme
SylvainJuge Dec 5, 2019
8f15e57
update doc
SylvainJuge Dec 5, 2019
f8200f7
Bump process plugin version to current snapshot
theobisproject Dec 7, 2019
28ca9bf
fix generated config asciidoc
SylvainJuge Dec 9, 2019
83a3b9c
replace todo with link to issue
SylvainJuge Dec 12, 2019
380f6c2
post review changes
SylvainJuge Dec 12, 2019
0aa056e
add check for no leaked transaction
SylvainJuge Dec 12, 2019
509a457
add multiple process exec strategies
SylvainJuge Dec 12, 2019
4e5b37e
rename process end helper
SylvainJuge Dec 12, 2019
a8b4936
report exceptions on process start
SylvainJuge Dec 12, 2019
1b53813
terminate process span on 'destroy'
SylvainJuge Dec 16, 2019
e5c4922
faster class matching for instrumentation
SylvainJuge Dec 19, 2019
27052f4
Merge branch 'master' into binary-execution-support
SylvainJuge Dec 19, 2019
6a49a2e
update doc
SylvainJuge Dec 19, 2019
60248a0
update supported technologies
SylvainJuge Dec 19, 2019
0a4c4e3
minor changes from PR review
SylvainJuge Dec 20, 2019
6238402
use inline weak map cleanup
SylvainJuge Dec 20, 2019
44bb215
Remove diamond operator
eyalkoren Dec 22, 2019
912a9c4
Merge remote-tracking branch 'upstream/master' into binary-execution-…
eyalkoren Jan 8, 2020
41b55db
Updating configuration.asciidoc
eyalkoren Jan 8, 2020
e7c27b8
Reducing apache scope to test
eyalkoren Jan 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ endif::[]

* Add support for <<supported-databases, Redis Lettuce client>>
* Add `context.message.age.ms` field for JMS message receiving spans and transactions - {pull}970[#970]
* Instrument log4j Logger#error(String, Throwable) (#919)
* Instrument log4j Logger#error(String, Throwable) ({pull}919[#919])
Automatically captures exceptions when calling `logger.error("message", exception)`
* Add instrumentation for external process execution through `java.lang.Process` and Apache `commons-exec` - {pull}903[#903]

[float]
===== Bug Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
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.not;

public class ElasticApmAgent {
Expand Down Expand Up @@ -342,11 +343,16 @@ private static AgentBuilder getAgentBuilder(final ByteBuddy byteBuddy, final Cor
: AgentBuilder.PoolStrategy.Default.FAST)
.ignore(any(), isReflectionClassLoader())
.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"))
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ public <T extends ConfigurationOptionProvider> T getConfig(Class<T> pluginClass)
return configurationRegistry.getConfig(pluginClass);
}

@SuppressWarnings("ReferenceEquality")
public void endTransaction(Transaction transaction) {
if (logger.isDebugEnabled()) {
logger.debug("} endTransaction {}", transaction);
Expand All @@ -408,7 +407,6 @@ public void endTransaction(Transaction transaction) {
}
}

@SuppressWarnings("ReferenceEquality")
public void endSpan(Span span) {
if (span.isSampled() && !span.isDiscard()) {
long spanFramesMinDurationMs = stacktraceConfiguration.getSpanFramesMinDurationMs();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Expand Down Expand Up @@ -91,6 +91,9 @@ public final void resetReporter() {
@AfterEach
public final void cleanUp() {
tracer.resetServiceNameOverrides();
assertThat(tracer.getActive()).isNull();

assertThat(tracer.getActive())
.describedAs("nothing should be left active at end of test, failure will likely indicate a span/transaction still active")
.isNull();
}
}
21 changes: 21 additions & 0 deletions apm-agent-plugins/apm-process-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Process plugin
eyalkoren marked this conversation as resolved.
Show resolved Hide resolved

This plugin creates spans for external processes executed by the JVM, which use the `java.lang.Process` class.

[Apache commons-exec](https://commons.apache.org/proper/commons-exec/) library support is included.

## Limitations

`java.lang.ProcessHandler` and `java.lang.Process.toHandle()` introduced in java 9 are not
instrumented. As a result, process execution using this API is not yet supported.

## Implementation Notes

Instrumentation of classes in `java.lang.*` that are loaded in the bootstrap classloader can't
be tested with unit tests due to the fact that agent is loaded in application/system classloader
for those tests.

Thus, using integration tests is required to test the instrumentation end-to-end.
Also, in order to provide a good test of this feature without testing everything in integration tests, we
- delegate most of the advice code to helper classes
- test extensively those classes with regular unit tests
27 changes: 27 additions & 0 deletions apm-agent-plugins/apm-process-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>apm-agent-plugins</artifactId>
<groupId>co.elastic.apm</groupId>
<version>1.12.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<properties>
<apm-agent-parent.base.dir>${project.basedir}/../..</apm-agent-parent.base.dir>
</properties>

<artifactId>apm-process-plugin</artifactId>
<name>${project.groupId}:${project.artifactId}</name>

<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 - 2019 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.process;

import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
import net.bytebuddy.matcher.ElementMatcher;

import java.util.Arrays;
import java.util.Collection;

import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;

public abstract class BaseProcessInstrumentation extends ElasticApmInstrumentation {

@Override
public final ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() {
// java.lang.* is loaded from bootstrap classloader
return isBootstrapClassLoader();
}

@Override
public final Collection<String> getInstrumentationGroupNames() {
return Arrays.asList("process", "incubating");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 - 2019 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.process;

import co.elastic.apm.agent.bci.ElasticApmInstrumentation;
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 java.util.Arrays;
import java.util.Collection;

import static net.bytebuddy.matcher.ElementMatchers.hasSuperClass;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

/**
* Provides context propagation for apache commons-exec library that delegates to a background thread for
* asynchronous process execution. Synchronous execution is already covered with {@link Process} instrumentation.
* <p>
* Instruments {@code org.apache.commons.exec.DefaultExecutor#createThread(Runnable, String)} and any direct subclass
* that overrides it.
*/
public class CommonsExecAsyncInstrumentation extends ElasticApmInstrumentation {

private static final String DEFAULT_EXECUTOR_CLASS = "org.apache.commons.exec.DefaultExecutor";
// only known subclass of default implementation
private static final String DAEMON_EXECUTOR_CLASS = "org.apache.commons.exec.DaemonExecutor";

@Override
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
// Most implementations are likely to have 'Executor' in their name, which will work most of the time
// while not perfect this allows to avoid the expensive 'hasSuperClass' in most cases
return nameContains("Executor");
}

@Override
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
// instrument default implementation and direct subclasses
return named(DEFAULT_EXECUTOR_CLASS)
.or(named(DAEMON_EXECUTOR_CLASS))
// this super class check is expensive
.or(hasSuperClass(named(DEFAULT_EXECUTOR_CLASS)));
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
return named("createThread")
.and(takesArgument(0, Runnable.class));
}

@Override
public Collection<String> getInstrumentationGroupNames() {
// part of 'process' group, as usage is not relevant without it, relies on generic Process instrumentation
return Arrays.asList("apache-commons-exec", "process", "incubating");
}

@Override
public Class<?> getAdviceClass() {
return CommonsExecAdvice.class;
}

public static final class CommonsExecAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
private static void onEnter(@Advice.Argument(value = 0, readOnly = false) Runnable runnable) {
if (tracer == null || tracer.getActive() == null) {
return;
}
// context propagation is done by wrapping existing runnable argument

//noinspection UnusedAssignment
runnable = tracer.getActive().withActive(runnable);
}
}
}
Loading