diff --git a/.gitignore b/.gitignore index b0415e746..aaa1e66e4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,7 @@ _site/ *.iml *.swp .factorypath -*.log -.checkstyle \ No newline at end of file +*.logtjmeter +.checkstyle +.DS_Store +*.log \ No newline at end of file diff --git a/benchmarks/README.adoc b/benchmarks/README.adoc new file mode 100644 index 000000000..d515d8164 --- /dev/null +++ b/benchmarks/README.adoc @@ -0,0 +1,38 @@ +== Spring Cloud Sleuth Benchmarks + +This module can run benchmarks using the following tools + +- JMH +- JMeter + +=== How to run it? + +In the root folder inside the `scripts` folder there are the following benchmark scripts: + +- runJmhBenchmark.sh +- runJmeterBenchmarks.sh + +Just execute them from the root folder like this: + +[source] +---- +./scripts/runJmeterBenchmarks.sh +./scripts/runJmhBenchmarks.sh +---- + +=== How do they work? + +For JMH we're building a shaded JAR file that is next executed. + +For JMeter we're running two applications, one with Sleuth, one without with Spring Boot +Maven Plugin. Next a Maven JMeter plugin is executed that loads the `*.jmx` files, starts +JMeter and prints out the results. At the end Spring Boot Maven Plugin stops the applications. + +=== What are we testing? + +For now we're testing the performance impact of the following: + +- our custom Trace HTTP filter +- instrumentation of controllers - `@Callable` returning and the sync one +- instrumentation of RestTemplate +- `@Async` annotated methods \ No newline at end of file diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml new file mode 100644 index 000000000..f5178c3ac --- /dev/null +++ b/benchmarks/pom.xml @@ -0,0 +1,575 @@ + + + + 4.0.0 + + Benchmarks + Benchmarks (JMH) + org.springframework.cloud + 1.0.0.BUILD-SNAPSHOT + benchmarks + + + ${project.basedir}/.. + 1.11.3 + 2.4.3 + 2.5.2 + true + 1.8 + 1.8 + + + + + + org.springframework.cloud + spring-cloud-sleuth-dependencies + ${project.version} + pom + import + + + + org.springframework.boot + spring-boot-dependencies + 1.3.3.RELEASE + pom + import + + + + + + + org.springframework.cloud + spring-cloud-sleuth-dependencies + ${project.version} + pom + import + + + ${project.groupId} + spring-cloud-sleuth-core + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-configuration-processor + + + org.springframework.cloud + spring-cloud-starter-feign + + + org.springframework.cloud + spring-cloud-starter-zuul + + + org.springframework.integration + spring-integration-core + + + + org.springframework + spring-test + compile + + + org.aspectj + aspectjrt + + + org.aspectj + aspectjweaver + + + org.assertj + assertj-core + 2.1.0 + compile + + + org.hamcrest + hamcrest-core + 1.3 + compile + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + maven-deploy-plugin + + true + + + + maven-install-plugin + ${maven-install-plugin.version} + + true + + + + + + + + spring-snapshots + Spring Snapshots + http://repo.spring.io/libs-snapshot-local + + true + + + + spring-milestones + Spring Milestones + http://repo.spring.io/libs-milestone-local + + false + + + + spring-staging + Spring Staging + http://repo.spring.io/libs-staging-local/ + + false + + + + spring-releases + Spring Releases + http://repo.spring.io/release + + false + + + + + + + jmh + + false + + + + + maven-shade-plugin + ${maven-shade-plugin.version} + + + org.springframework.boot + spring-boot-maven-plugin + 1.3.3.RELEASE + + + + true + + true + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + package + + shade + + + benchmarks + + + META-INF/spring.handlers + + + META-INF/spring.factories + + + META-INF/spring.schemas + + + + org.openjdk.jmh.Main + + + false + + + + + + + + + jmeter + + false + + + + + org.springframework.boot + spring-boot-maven-plugin + 1.3.3.RELEASE + + + + repackage + + + + start-non-sleuth-app + pre-integration-test + + start + + + "-Dserver.port=9875" "-Dspring.sleuth.enabled=false" + 8875 + true + + + + stop-non-sleuth-app + post-integration-test + + stop + + + 8875 + true + + + + start-sleuth-app + pre-integration-test + + start + + + "-Dserver.port=9876" + 8876 + true + + + + stop-sleuth-app + post-integration-test + + stop + + + 8876 + true + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.19.1 + + + + integration-test + verify + + + + + + com.lazerycode.jmeter + jmeter-maven-plugin + 1.10.1 + + false + false + true + + true + true + + + + kg.apc + jmeter-plugins + + + + + + execute-jmeter-tests + + jmeter + + integration-test + + + + + kg.apc + jmeter-plugins + 1.0.0 + + + + kg.apc + perfmon + + + org.apache.hadoop + hadoop-core + + + org.apache.hbase + hbase + + + + + org.apache.jmeter + jorphan + + + org.apache.bsf + bsf-api + + + org.bouncycastle + bcmail-jdk15 + + + org.bouncycastle + bcprov-jdk15 + + + javax.activation + activation + + + commons-logging + commons-logging + + + + + + + com.lazerycode.jmeter + jmeter-analysis-maven-plugin + 1.0.6 + + ${project.build.directory}/jmeter/results/analysis/ + + + + create-html-report-for-asynchttp + verify + + analyze + + + ${project.build.directory}/jmeter/results/AsyncHttpBenchmarks.jtl + + + + create-html-report-for-async-method + verify + + analyze + + + ${project.build.directory}/jmeter/results/AsyncMethodBenchmarks.jtl + + + + create-html-report-for-sync-http + verify + + analyze + + + ${project.build.directory}/jmeter/results/SyncHttpBenchmarks.jtl + + + + create-html-report-for-nice-index-html + verify + + analyze + + + ${project.build.directory}/jmeter/results/*.jtl + + + + + + de.codecentric + jmeter-graph-maven-plugin + 0.1.0 + + + create-graph-threads-for-asynchttp + + create-graph + + verify + + ${project.build.directory}/jmeter/results/AsyncHttpBenchmarks.jtl + + + ResponseTimesOverTime + 800 + 600 + ${project.build.directory}/jmeter/results/AsyncHttpBenchmarks-ResponseTimesOverTime.png + + + ResponseTimesPercentiles + 800 + 600 + ${project.build.directory}/jmeter/results/AsyncHttpBenchmarks-ResponseTimesPercentiles.png + + + LatenciesOverTime + 800 + 600 + ${project.build.directory}/jmeter/results/AsyncHttpBenchmarks-LatenciesOverTime.png + + + + + + create-graph-threads-for-async-method + + create-graph + + verify + + ${project.build.directory}/jmeter/results/AsyncMethodBenchmarks.jtl + + + ResponseTimesOverTime + 800 + 600 + ${project.build.directory}/jmeter/results/AsyncMethodBenchmarks-ResponseTimesOverTime.png + + + ResponseTimesPercentiles + 800 + 600 + ${project.build.directory}/jmeter/results/AsyncMethodBenchmarks-ResponseTimesPercentiles.png + + + LatenciesOverTime + 800 + 600 + ${project.build.directory}/jmeter/results/AsyncMethodBenchmarks-LatenciesOverTime.png + + + + + + create-graph-threads-for-sync-http + + create-graph + + verify + + ${project.build.directory}/jmeter/results/SyncHttpBenchmarks.jtl + + + ResponseTimesOverTime + 800 + 600 + ${project.build.directory}/jmeter/results/SyncHttpBenchmarks-ResponseTimesOverTime.png + + + ResponseTimesPercentiles + 800 + 600 + ${project.build.directory}/jmeter/results/SyncHttpBenchmarks-ResponseTimesPercentiles.png + + + LatenciesOverTime + 800 + 600 + ${project.build.directory}/jmeter/results/SyncHttpBenchmarks-LatenciesOverTime.png + + + + + + + + + + + diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java new file mode 100644 index 000000000..83eb20103 --- /dev/null +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/app/SleuthBenchmarkingSpringApp.java @@ -0,0 +1,108 @@ +/* + * Copyright 2013-2016 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. + */ + +package org.springframework.cloud.sleuth.benchmarks.app; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import javax.annotation.PreDestroy; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.cloud.sleuth.Sampler; +import org.springframework.cloud.sleuth.sampler.AlwaysSampler; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.util.SocketUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Marcin Grzejszczak + */ +@SpringBootApplication +@RestController +@EnableAsync +public class SleuthBenchmarkingSpringApp implements + ApplicationListener { + + private static final Log log = LogFactory.getLog(SleuthBenchmarkingSpringApp.class); + + public final ExecutorService pool = Executors.newWorkStealingPool(); + + public int port; + + @RequestMapping("/foo") + public String foo() { + return "foo"; + } + + @RequestMapping("/bar") + public Callable bar() { + return () -> "bar"; + } + + @RequestMapping("/async") + public String asyncHttp() throws ExecutionException, InterruptedException { + return this.async().get(); + } + + @Async + public Future async() { + return this.pool.submit(() -> "async"); + } + + @Override + public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) { + this.port = event.getEmbeddedServletContainer().getPort(); + } + + @Bean + public EmbeddedServletContainerFactory servletContainer(@Value("${server.port:0}") int serverPort) { + log.info("Starting container at port [" + serverPort + "]"); + return new TomcatEmbeddedServletContainerFactory(serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort); + } + + @PreDestroy + public void clean() { + this.pool.shutdownNow(); + } + + @Bean + public Sampler alwaysSampler() { + return new AlwaysSampler(); + } + + public ExecutorService getPool() { + return this.pool; + } + + public static void main(String... args) { + SpringApplication.run(SleuthBenchmarkingSpringApp.class, args); + } +} diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java new file mode 100644 index 000000000..4e35d5f10 --- /dev/null +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/AsyncBenchmarks.java @@ -0,0 +1,89 @@ +/* + * Copyright 2013-2016 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. + */ +package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +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.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.springframework.boot.SpringApplication; +import org.springframework.cloud.sleuth.benchmarks.app.SleuthBenchmarkingSpringApp; +import org.springframework.context.ConfigurableApplicationContext; + +import static org.assertj.core.api.BDDAssertions.then; + +@Measurement(iterations = 5) +@Warmup(iterations = 10) +@Fork(3) +@BenchmarkMode(Mode.SampleTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Threads(Threads.MAX) +public class AsyncBenchmarks { + + @State(Scope.Benchmark) + public static class BenchmarkContext { + volatile ConfigurableApplicationContext withSleuth; + volatile ConfigurableApplicationContext withoutSleuth; + volatile SleuthBenchmarkingSpringApp tracedAsyncMethodHavingBean; + volatile SleuthBenchmarkingSpringApp untracedAsyncMethodHavingBean; + + @Setup public void setup() { + this.withSleuth = new SpringApplication( + SleuthBenchmarkingSpringApp.class) + .run("--spring.jmx.enabled=false", + "--spring.application.name=withSleuth"); + this.withoutSleuth = new SpringApplication( + SleuthBenchmarkingSpringApp.class) + .run("--spring.jmx.enabled=false", + "--spring.application.name=withoutSleuth", + "--spring.sleuth.enabled=false", + "--spring.sleuth.async.enabled=false"); + this.tracedAsyncMethodHavingBean = this.withSleuth.getBean( + SleuthBenchmarkingSpringApp.class); + this.untracedAsyncMethodHavingBean = this.withoutSleuth.getBean( + SleuthBenchmarkingSpringApp.class); + } + + @TearDown public void clean() { + this.tracedAsyncMethodHavingBean.clean(); + this.untracedAsyncMethodHavingBean.clean(); + this.withSleuth.close(); + this.withoutSleuth.close(); + } + } + + @Benchmark + public void asyncMethodWithoutSleuth(BenchmarkContext context) + throws Exception { + then(context.untracedAsyncMethodHavingBean.async().get()).isEqualTo("async"); + } + + @Benchmark + public void asyncMethodWithSleuth(BenchmarkContext context) + throws Exception { + then(context.tracedAsyncMethodHavingBean.async().get()).isEqualTo("async"); + } +} \ No newline at end of file diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java new file mode 100644 index 000000000..a42e5a607 --- /dev/null +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/HttpFilterBenchmarks.java @@ -0,0 +1,171 @@ +/* + * Copyright 2013-2016 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. + */ +package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +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.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.springframework.boot.SpringApplication; +import org.springframework.cloud.sleuth.benchmarks.app.SleuthBenchmarkingSpringApp; +import org.springframework.cloud.sleuth.instrument.web.TraceFilter; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Warmup(iterations = 10) +@BenchmarkMode(Mode.SampleTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Threads(Threads.MAX) +public class HttpFilterBenchmarks { + + @State(Scope.Benchmark) + public static class BenchmarkContext { + volatile ConfigurableApplicationContext withSleuth; + volatile DummyFilter dummyFilter = new DummyFilter(); + volatile TraceFilter traceFilter; + volatile MockMvc mockMvcForTracedController; + volatile MockMvc mockMvcForUntracedController; + + @Setup public void setup() { + this.withSleuth = new SpringApplication( + SleuthBenchmarkingSpringApp.class) + .run("--spring.jmx.enabled=false", + "--spring.application.name=withSleuth"); + this.traceFilter = this.withSleuth.getBean(TraceFilter.class); + this.mockMvcForTracedController = MockMvcBuilders.standaloneSetup( + this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class)) + .build(); + this.mockMvcForUntracedController = MockMvcBuilders.standaloneSetup( + new VanillaController()) + .build(); + } + + + @TearDown public void clean() { + this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class).clean(); + this.withSleuth.close(); + } + } + + @Benchmark + @Measurement(iterations = 5, time = 1) + @Fork(3) + public void filterWithoutSleuth(BenchmarkContext context) + throws IOException, ServletException { + MockHttpServletRequest request = builder().buildRequest(new MockServletContext()); + MockHttpServletResponse response = new MockHttpServletResponse(); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + context.dummyFilter.doFilter(request, response, new MockFilterChain()); + } + + @Benchmark + @Measurement(iterations = 5, time = 1) + @Fork(3) + public void filterWithSleuth(BenchmarkContext context) + throws ServletException, IOException { + MockHttpServletRequest request = builder().buildRequest(new MockServletContext()); + MockHttpServletResponse response = new MockHttpServletResponse(); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + context.traceFilter.doFilter(request, response, new MockFilterChain()); + } + + @Benchmark + @Measurement(iterations = 5, time = 10) + @Fork(10) + public void asyncWithoutSleuth(BenchmarkContext context) throws Exception { + performRequest(context.mockMvcForUntracedController, "vanilla", "vanilla"); + } + + @Benchmark + @Measurement(iterations = 5, time = 10) + @Fork(10) + public void asyncWithSleuth(BenchmarkContext context) throws Exception { + performRequest(context.mockMvcForTracedController, "bar", "bar"); + } + + private MockHttpServletRequestBuilder builder() { + return get("/").accept(MediaType.APPLICATION_JSON) + .header("User-Agent", "MockMvc"); + } + + private void performRequest(MockMvc mockMvc, String url, String expectedResult) throws Exception { + MvcResult mvcResult = mockMvc.perform(get("/" + url)) + .andExpect(status().isOk()) + .andExpect(request().asyncStarted()) + .andReturn(); + + mockMvc.perform(asyncDispatch(mvcResult)) + .andExpect(status().isOk()) + .andExpect(content().string(expectedResult)); + } + + private static class DummyFilter implements Filter { + + @Override public void init(FilterConfig filterConfig) throws ServletException {} + + @Override public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + chain.doFilter(request, response); + } + + @Override public void destroy() { } + } + + @RestController + private static class VanillaController { + @RequestMapping("/vanilla") + public Callable vanilla() { + return () -> "vanilla"; + } + } +} \ No newline at end of file diff --git a/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java new file mode 100644 index 000000000..76b2aa2b1 --- /dev/null +++ b/benchmarks/src/main/java/org/springframework/cloud/sleuth/benchmarks/jmh/benchmarks/RestTemplateBenchmark.java @@ -0,0 +1,100 @@ +/* + * Copyright 2013-2016 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. + */ +package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks; + +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +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.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.springframework.boot.SpringApplication; +import org.springframework.cloud.sleuth.benchmarks.app.SleuthBenchmarkingSpringApp; +import org.springframework.cloud.sleuth.instrument.web.client.TraceRestTemplateInterceptor; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.web.client.MockMvcClientHttpRequestFactory; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.BDDAssertions.then; + +/** + * We're checking how much overhead does the instrumentation + * of the RestTemplate take + */ +@Measurement(iterations = 5) +@Warmup(iterations = 10) +@Fork(3) +@BenchmarkMode(Mode.SampleTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Threads(Threads.MAX) +public class RestTemplateBenchmark { + + @State(Scope.Benchmark) + public static class BenchmarkContext { + volatile ConfigurableApplicationContext withSleuth; + volatile MockMvc mockMvc; + volatile RestTemplate tracedTemplate; + volatile RestTemplate untracedTemplate; + + @Setup public void setup() { + this.withSleuth = new SpringApplication( + SleuthBenchmarkingSpringApp.class) + .run("--spring.jmx.enabled=false", + "--spring.application.name=withSleuth"); + this.mockMvc = MockMvcBuilders.standaloneSetup( + this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class)) + .build(); + this.tracedTemplate = new RestTemplate( + new MockMvcClientHttpRequestFactory(this.mockMvc)); + this.tracedTemplate.setInterceptors(Collections.singletonList( + this.withSleuth.getBean(TraceRestTemplateInterceptor.class))); + this.untracedTemplate = new RestTemplate( + new MockMvcClientHttpRequestFactory(this.mockMvc)); + } + + @TearDown public void clean() { + this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class).clean(); + this.withSleuth.close(); + } + } + + @Benchmark + public void syncEndpointWithoutSleuth(BenchmarkContext context) + throws IOException, ServletException { + then(context.untracedTemplate.getForObject("/foo", String.class)).isEqualTo("foo"); + } + + @Benchmark + public void syncEndpointWithSleuth(BenchmarkContext context) + throws ServletException, IOException { + then(context.tracedTemplate.getForObject("/foo", String.class)).isEqualTo("foo"); + } + +} \ No newline at end of file diff --git a/benchmarks/src/main/resources/application.yml b/benchmarks/src/main/resources/application.yml new file mode 100644 index 000000000..81bf8e0be --- /dev/null +++ b/benchmarks/src/main/resources/application.yml @@ -0,0 +1,3 @@ +logging.level: + org.springframework: ERROR + org.springframework.cloud.sleuth.benchmarks: INFO \ No newline at end of file diff --git a/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java b/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java new file mode 100644 index 000000000..3d95f4a5a --- /dev/null +++ b/benchmarks/src/test/java/org/springframework/cloud/sleuth/benchmarks/jmh/RunSleuthJmhBenchmarksFromIde.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2016 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. + */ + +package org.springframework.cloud.sleuth.benchmarks.jmh; + +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +public class RunSleuthJmhBenchmarksFromIde { + + // Convenience main entry-point for testing from IDE + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(RunSleuthJmhBenchmarksFromIde.class.getPackage().getName() + ".benchmarks.*") + .build(); + + new Runner(opt).run(); + } +} \ No newline at end of file diff --git a/benchmarks/src/test/jmeter/AsyncHttpBenchmarks.jmx b/benchmarks/src/test/jmeter/AsyncHttpBenchmarks.jmx new file mode 100644 index 000000000..cc20c8c11 --- /dev/null +++ b/benchmarks/src/test/jmeter/AsyncHttpBenchmarks.jmx @@ -0,0 +1,334 @@ + + + + + + false + false + + + + + + + + + + + localhost + + + + + + + 4 + + + + continue + + false + 1000 + + 100 + 5 + 1458745466000 + 1458745466000 + false + + + + + + + + + + 9875 + + + + + /bar + GET + true + false + true + false + false + + + + + + + + + 9876 + + + + + /bar + GET + true + false + true + false + false + + + + + + bar + + Assertion.response_data + false + 8 + all + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + 100 + 0 + 4 + HH:mm:ss.SSS + 10 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + diff --git a/benchmarks/src/test/jmeter/AsyncMethodBenchmarks.jmx b/benchmarks/src/test/jmeter/AsyncMethodBenchmarks.jmx new file mode 100644 index 000000000..104d89948 --- /dev/null +++ b/benchmarks/src/test/jmeter/AsyncMethodBenchmarks.jmx @@ -0,0 +1,334 @@ + + + + + + false + false + + + + + + + + + + + localhost + + + + + + + 4 + + + + continue + + false + 1000 + + 100 + 20 + 1458745466000 + 1458745466000 + false + + + + + + + + + + 9875 + + + + + /async + GET + true + false + true + false + false + + + + + + + + + 9876 + + + + + /async + GET + true + false + true + false + false + + + + + + async + + Assertion.response_data + false + 8 + all + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + 100 + 0 + 4 + HH:mm:ss.SSS + 10 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + diff --git a/benchmarks/src/test/jmeter/SyncHttpBenchmarks.jmx b/benchmarks/src/test/jmeter/SyncHttpBenchmarks.jmx new file mode 100644 index 000000000..786ecaa9f --- /dev/null +++ b/benchmarks/src/test/jmeter/SyncHttpBenchmarks.jmx @@ -0,0 +1,334 @@ + + + + + + false + false + + + + + + + + + + + localhost + + + + + + + 4 + + + + continue + + false + 1000 + + 100 + 20 + 1458745466000 + 1458745466000 + false + + + + + + + + + + 9875 + + + + + /foo + GET + true + false + true + false + false + + + + + + + + + 9876 + + + + + /foo + GET + true + false + true + false + false + + + + + + foo + + Assertion.response_data + false + 8 + all + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + 100 + 0 + 4 + HH:mm:ss.SSS + 10 + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + false + false + false + false + false + 0 + true + true + + + + + + + + diff --git a/benchmarks/src/test/jmeter/jmeter.properties b/benchmarks/src/test/jmeter/jmeter.properties new file mode 100644 index 000000000..921b80604 --- /dev/null +++ b/benchmarks/src/test/jmeter/jmeter.properties @@ -0,0 +1,17 @@ +# +# Copyright 2013-2016 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. +# + +sampleresult.useNanoTime=true diff --git a/pom.xml b/pom.xml index bc38a1d8d..8abf1586a 100644 --- a/pom.xml +++ b/pom.xml @@ -231,6 +231,14 @@ false + + spring-staging + Spring Staging + http://repo.spring.io/libs-staging-local/ + + false + + spring-releases Spring Releases @@ -278,6 +286,15 @@ + + benchmarks + + false + + + benchmarks + + diff --git a/scripts/runJmeterBenchmarks.sh b/scripts/runJmeterBenchmarks.sh new file mode 100755 index 000000000..45de29c8a --- /dev/null +++ b/scripts/runJmeterBenchmarks.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +echo "Killing the remaining apps - if something went wrong previously" +pkill -f SleuthBenchmarkingSpringApp || echo "No apps to kill" + +echo "Running JMeter Benchmarks" +./mvnw clean verify --projects benchmarks --also-make -Pbenchmarks,jmeter +echo "Killing the remaining apps - if something went wrong after the tests" +pkill -f SleuthBenchmarkingSpringApp || echo "No apps to kill" \ No newline at end of file diff --git a/scripts/runJmhBenchmarks.sh b/scripts/runJmhBenchmarks.sh new file mode 100755 index 000000000..3467056c4 --- /dev/null +++ b/scripts/runJmhBenchmarks.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "Running JMH Benchmarks" +./mvnw clean install -DskipTests --projects benchmarks --also-make -Pbenchmarks,jmh +java -Djmh.ignoreLock=true -jar benchmarks/target/benchmarks.jar org.springframework.cloud.sleuth.benchmarks.jmh.* | tee target/benchmarks.log \ No newline at end of file diff --git a/spring-cloud-sleuth-samples/pom.xml b/spring-cloud-sleuth-samples/pom.xml index dd31b3e60..7da692f44 100644 --- a/spring-cloud-sleuth-samples/pom.xml +++ b/spring-cloud-sleuth-samples/pom.xml @@ -41,10 +41,6 @@ - - 2.1.0 - -