Skip to content

Commit

Permalink
JDBC instrumentation
Browse files Browse the repository at this point in the history
closes elastic#35

Signed-off-by: Felix Barnsteiner <felix.barnsteiner@elastic.co>
  • Loading branch information
felixbarny committed May 14, 2018
1 parent 274b42c commit 883d650
Show file tree
Hide file tree
Showing 16 changed files with 573 additions and 70 deletions.
5 changes: 5 additions & 0 deletions apm-agent-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@
<artifactId>jackson-dataformat-protobuf</artifactId>
<version>${version.jackson}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>${version.byte-buddy}</version>
</dependency>
</dependencies>

<properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 the original author or authors
* %%
* Licensed 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.benchmark;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.runner.RunnerException;

import javax.servlet.ServletException;
import java.io.IOException;

public class ElasticApmActiveContinuousBenchmark extends ElasticApmContinuousBenchmark {

public ElasticApmActiveContinuousBenchmark() {
super(true);
}

public static void main(String[] args) throws RunnerException {
run(ElasticApmActiveContinuousBenchmark.class);
}

@Benchmark
public int benchmarkWithApm() throws IOException, ServletException {
httpServlet.service(request, response);
return response.getStatus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,30 @@
*/
package co.elastic.apm.benchmark;

import co.elastic.apm.bci.ElasticApmAgent;
import co.elastic.apm.configuration.CoreConfiguration;
import co.elastic.apm.impl.ElasticApmTracer;
import co.elastic.apm.report.Reporter;
import co.elastic.apm.servlet.ApmFilter;
import io.undertow.Undertow;
import org.openjdk.jmh.annotations.Benchmark;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.runner.RunnerException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.stagemonitor.configuration.ConfigurationOptionProvider;
import org.stagemonitor.configuration.ConfigurationRegistry;
import org.stagemonitor.configuration.source.SimpleSource;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.DriverManager;
Expand Down Expand Up @@ -97,20 +94,17 @@
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class ElasticApmContinuousBenchmark extends AbstractBenchmark {
public abstract class ElasticApmContinuousBenchmark extends AbstractBenchmark {

private ApmFilter apmFilter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private FilterChain filterChainWithApm;
private final boolean apmEnabled;
protected MockHttpServletRequest request;
protected MockHttpServletResponse response;
protected HttpServlet httpServlet;
private Undertow server;
private ElasticApmTracer tracer;
private Connection connectionWithApm;
private Connection connectionWithoutApm;
private FilterChain filterChainWithoutApm;

public static void main(String[] args) throws RunnerException {
run(ElasticApmContinuousBenchmark.class);
public ElasticApmContinuousBenchmark(boolean apmEnabled) {
this.apmEnabled = apmEnabled;
}

@Setup
Expand All @@ -124,42 +118,28 @@ public void setUp() throws SQLException {
.configurationRegistry(ConfigurationRegistry.builder()
.addConfigSource(new SimpleSource()
.add(CoreConfiguration.SERVICE_NAME, "benchmark")
.add(CoreConfiguration.INSTRUMENT, Boolean.toString(apmEnabled))
.add(CoreConfiguration.ACTIVE, Boolean.toString(apmEnabled))
.add("server_url", "http://localhost:" + port))
.optionProviders(ServiceLoader.load(ConfigurationOptionProvider.class))
.build())
.build()
.register();
apmFilter = new ApmFilter(tracer);
ElasticApmAgent.initInstrumentation(tracer, ByteBuddyAgent.install());
request = createRequest();
response = createResponse();

connectionWithApm = DriverManager.getConnection("jdbc:p6spy:h2:mem:test", "user", "");
connectionWithApm.createStatement().execute("CREATE TABLE IF NOT EXISTS ELASTIC_APM (FOO INT, BAR VARCHAR(255))");
connectionWithApm.createStatement().execute("INSERT INTO ELASTIC_APM (FOO, BAR) VALUES (1, 'APM')");
filterChainWithApm = new BenchmarkingFilterChain(connectionWithApm, tracer);

connectionWithoutApm = DriverManager.getConnection("jdbc:h2:mem:test", "user", "");
connectionWithoutApm.createStatement().execute("CREATE TABLE IF NOT EXISTS ELASTIC_APM (FOO INT, BAR VARCHAR(255))");
connectionWithoutApm.createStatement().execute("INSERT INTO ELASTIC_APM (FOO, BAR) VALUES (1, 'APM')");
filterChainWithoutApm = new BenchmarkingFilterChain(connectionWithoutApm, tracer);
Connection connection = DriverManager.getConnection("jdbc:h2:mem:test", "user", "");
connection.createStatement().execute("CREATE TABLE IF NOT EXISTS ELASTIC_APM (FOO INT, BAR VARCHAR(255))");
connection.createStatement().execute("INSERT INTO ELASTIC_APM (FOO, BAR) VALUES (1, 'APM')");
httpServlet = new BenchmarkingServlet(connection, tracer);
}

@TearDown
public void tearDown() {
server.stop();
tracer.stop();
}

@Benchmark
public int benchmarkWithApm() throws IOException, ServletException {
apmFilter.doFilter(request, response, filterChainWithApm);
return response.getStatus();
}

@Benchmark
public int benchmarkWithoutApm() throws IOException, ServletException {
filterChainWithoutApm.doFilter(request, response);
return response.getStatus();
ElasticApmAgent.reset();
}

private MockHttpServletRequest createRequest() {
Expand Down Expand Up @@ -231,18 +211,18 @@ private MockHttpServletResponse createResponse() {
return new MockHttpServletResponse();
}

private static class BenchmarkingFilterChain implements FilterChain {
private static class BenchmarkingServlet extends HttpServlet {

private final Connection connection;
private final Reporter reporter;

private BenchmarkingFilterChain(Connection connection, ElasticApmTracer tracer) {
private BenchmarkingServlet(Connection connection, ElasticApmTracer tracer) {
this.connection = connection;
reporter = tracer.getReporter();
}

@Override
public void doFilter(ServletRequest request, ServletResponse response) throws ServletException {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
try {
final PreparedStatement preparedStatement = connection
.prepareStatement("SELECT * FROM ELASTIC_APM WHERE foo=?");
Expand All @@ -254,7 +234,7 @@ public void doFilter(ServletRequest request, ServletResponse response) throws Se
}
// makes sure the jdbc query and the reporting can't be eliminated by JIT
// setting it as the http status code so that there are no allocations necessary
((HttpServletResponse) response).setStatus(count + reporter.getDropped());
response.setStatus(count + reporter.getDropped());
} catch (Exception e) {
throw new ServletException(e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 the original author or authors
* %%
* Licensed 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.benchmark;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.runner.RunnerException;

import javax.servlet.ServletException;
import java.io.IOException;

public class ElasticApmNotActiveContinuousBenchmark extends ElasticApmContinuousBenchmark {
public ElasticApmNotActiveContinuousBenchmark() {
super(false);
}

public static void main(String[] args) throws RunnerException {
run(ElasticApmNotActiveContinuousBenchmark.class);
}

@Benchmark
public int benchmarkWithoutApm() throws IOException, ServletException {
httpServlet.service(request, response);
return response.getStatus();
}
}
2 changes: 1 addition & 1 deletion apm-agent-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.8.11</version>
<version>${version.byte-buddy}</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* Licensed 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.
Expand All @@ -19,6 +19,8 @@
*/
package co.elastic.apm.bci;

import net.bytebuddy.asm.Advice;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand All @@ -27,6 +29,11 @@
/**
* A marker annotation which indicates that the annotated field or method has to be public because it is called by advice methods,
* which are inlined into other classes.
* <p>
* Also, when using non {@link Advice.OnMethodEnter#inline() inline}d advices,
* the signature of the advice method,
* as well as the advice class itself need to be public.
* </p>
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*-
* #%L
* Elastic APM Java agent
* %%
* Copyright (C) 2018 the original author or authors
* %%
* Licensed 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;

import co.elastic.apm.bci.ElasticApmAgent;
import co.elastic.apm.configuration.SpyConfiguration;
import co.elastic.apm.impl.ElasticApmTracer;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;

public abstract class AbstractInstrumentationTest {
protected static ElasticApmTracer tracer;
protected static MockReporter reporter;

@BeforeAll
static void beforeAll() {
reporter = new MockReporter();
tracer = ElasticApmTracer.builder()
.configurationRegistry(SpyConfiguration.createSpyConfig())
.reporter(reporter)
.build();
ElasticApmAgent.initInstrumentation(tracer, ByteBuddyAgent.install());
}

@AfterAll
static void afterAll() {
ElasticApmAgent.reset();
}

@BeforeEach
final void resetReporter() {
reporter.reset();
}
}
23 changes: 23 additions & 0 deletions apm-agent-plugins/apm-jdbc-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# JDBC plugin

This plugin creates spans for JDBC queries.

## Implementation Notes:

The JDBC API itself is loaded by the bootstrap class loader.

The implementations are however mostly loaded by
* The application server class loader (or a module class loader), if the server bundles the implementation
* The web app class loader, if the application bundles the JDBC driver
* The `SystemClassLoader`, when starting a simple `main` method Java program

As a consequence,
there are not lots of class loader issues,
as the implementations are loaded by a child of the `SystemClassLoader` or the `SystemClassLoader` itself,
which also loads the agent code.
But as we don't need to refer to implementation specific classes,
but only the JDBC API,
which is loaded by the bootstrap class loader,
which is a parent class loader of both the agent and the JDBC implementation,
we can fully reference it even in helper classes.

Loading

0 comments on commit 883d650

Please sign in to comment.