Improvement with Reactor instrumentation

- adds Spring Cloud Function instrumentation
- adds Operators to manually provide instrumentation for Fluxes
- introduces Manual instrumentation mode for Reactor

TODO: Documentation (will add it soon)

related gh-1684
This commit is contained in:
Marcin Grzejszczak
2020-07-21 19:03:14 +02:00
parent fb433b47de
commit bd149ce4a7
366 changed files with 2864 additions and 722 deletions

View File

@@ -25,16 +25,23 @@
<version>3.0.0-SNAPSHOT</version>
<artifactId>benchmarks</artifactId>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0-M1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<main.basedir>${project.basedir}/..</main.basedir>
<jmh.version>1.22</jmh.version>
<maven-shade-plugin.version>3.2.1</maven-shade-plugin.version>
<sonar.skip>true</sonar.skip>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring-boot.version>2.4.0-M1</spring-boot.version>
<brave.version>5.12.3</brave.version>
<okhttp.version>3.14.6</okhttp.version>
<microbenchmark-runner.version>0.2.0.RELEASE</microbenchmark-runner.version>
<jmh.version>1.21</jmh.version>
<spring-cloud-stream.version>3.1.0-SNAPSHOT</spring-cloud-stream.version>
</properties>
<dependencyManagement>
@@ -47,10 +54,9 @@
<scope>import</scope>
</dependency>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-dependencies</artifactId>
<version>${spring-cloud-stream.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -60,7 +66,7 @@
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-cloud-sleuth-core</artifactId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -91,22 +97,36 @@
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.14.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<type>test-jar</type>
<scope>compile</scope>
<classifier>test-binder</classifier>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<groupId>com.github.mp911de.microbenchmark-runner</groupId>
<artifactId>microbenchmark-runner-junit5</artifactId>
<version>${microbenchmark-runner.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.mp911de.microbenchmark-runner</groupId>
<artifactId>microbenchmark-runner-extras</artifactId>
<version>${microbenchmark-runner.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
@@ -122,7 +142,6 @@
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-instrumentation-httpclient</artifactId>
<version>${brave.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
@@ -140,24 +159,15 @@
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
@@ -229,75 +239,6 @@
</pluginRepositories>
<profiles>
<profile>
<id>jmh</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
<configuration>
<keepDependenciesWithProvidedScope>true
</keepDependenciesWithProvidedScope>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>benchmarks</finalName>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer
implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
<resource>META-INF/spring.factories</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
<createDependencyReducedPom>false
</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jmeter</id>
<activation>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -56,13 +56,15 @@ import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
@EnableAsync
public class SleuthBenchmarkingSpringApp
implements ApplicationListener<ServletWebServerInitializedEvent> {
public class SleuthBenchmarkingSpringApp implements ApplicationListener<ServletWebServerInitializedEvent> {
private static final Log log = LogFactory.getLog(SleuthBenchmarkingSpringApp.class);
public final ExecutorService pool = Executors.newWorkStealingPool();
/**
* Port of the app.
*/
public int port;
@Autowired(required = false)
@@ -109,11 +111,9 @@ public class SleuthBenchmarkingSpringApp
}
@Bean
public ServletWebServerFactory servletContainer(
@Value("${server.port:0}") int serverPort) {
public ServletWebServerFactory servletContainer(@Value("${server.port:0}") int serverPort) {
log.info("Starting container at port [" + serverPort + "]");
return new TomcatServletWebServerFactory(
serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort);
return new TomcatServletWebServerFactory(serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort);
}
@PreDestroy

View File

@@ -0,0 +1,281 @@
/*
* Copyright 2013-2020 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
*
* https://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.stream;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import brave.Tracing;
import brave.propagation.TraceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.instrument.messaging.MessagingSleuthOperator;
import org.springframework.cloud.sleuth.instrument.web.WebFluxSleuthOperators;
import org.springframework.cloud.stream.binder.test.InputDestination;
import org.springframework.cloud.stream.binder.test.OutputDestination;
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootApplication
@Import(TestChannelBinderConfiguration.class)
public class SleuthBenchmarkingStreamApplication {
public static void main(String[] args) throws InterruptedException, IOException {
// System.setProperty("spring.sleuth.enabled", "false");
// System.setProperty("spring.sleuth.reactor.instrumentation-type",
// "DECORATE_ON_EACH");
// System.setProperty("spring.sleuth.reactor.instrumentation-type",
// "DECORATE_ON_LAST");
// System.setProperty("spring.sleuth.reactor.instrumentation-type", "MANUAL");
System.setProperty("spring.sleuth.reactor.instrumentation-type", "MANUAL");
System.setProperty("spring.sleuth.function.type", "simple");
ConfigurableApplicationContext context = SpringApplication.run(SleuthBenchmarkingStreamApplication.class, args);
System.out.println("PRess any key to continue");
System.in.read();
for (int i = 0; i < 1; i++) {
InputDestination input = context.getBean(InputDestination.class);
input.send(MessageBuilder.withPayload("hello".getBytes())
.setHeader("b3", "4883117762eb9420-4883117762eb9420-1").build());
System.out.println("Retrieving the message for tests");
OutputDestination output = context.getBean(OutputDestination.class);
Message<byte[]> message = output.receive(200L);
System.out.println("Got the message from output");
assertThat(message).isNotNull();
System.out.println("Message is not null");
assertThat(message.getPayload()).isEqualTo("HELLO".getBytes());
System.out.println("Payload is HELLO");
String b3 = message.getHeaders().get("b3", String.class);
System.out.println("Checking the b3 header [" + b3 + "]");
assertThat(b3).startsWith("4883117762eb9420");
}
}
@Bean
ExecutorService sleuthExecutorService() {
return Executors.newCachedThreadPool();
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.function.type", havingValue = "simple")
public Function<String, String> nonReactiveSimpleSleuthFunction() {
System.out.println("simple_function");
return new SimpleFunction();
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.function.type", havingValue = "reactive_simple")
public Function<Flux<String>, Flux<String>> reactiveSimpleSleuthFunction() {
System.out.println("simple_reactive_function");
return new SimpleReactiveFunction();
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.function.type", havingValue = "simple_manual")
public Function<Message<String>, Message<String>> nonReactiveSimpleManualSleuthFunction(Tracing tracing) {
System.out.println("simple_manual_function");
return new SimpleManualFunction(tracing);
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.function.type", havingValue = "reactive_simple_manual")
public Function<Flux<Message<String>>, Flux<Message<String>>> reactiveSimpleManualSleuthFunction(Tracing tracing) {
System.out.println("simple_reactive_manual_function");
return new SimpleReactiveManualFunction(tracing);
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.nonreactive.function.enabled", havingValue = "true")
public Function<String, String> nonReactiveSleuthFunction(ExecutorService executorService) {
System.out.println("no sleuth non reactive function");
return new SleuthNonReactiveFunction(executorService);
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.function.type", havingValue = "DECORATE_ON_EACH",
matchIfMissing = true)
public Function<Flux<String>, Flux<String>> onEachFunction() {
System.out.println("on each function");
return new SleuthFunction();
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.function.type", havingValue = "DECORATE_ON_LAST")
public Function<Flux<String>, Flux<String>> onLastFunction() {
System.out.println("on last function");
return new SleuthFunction();
}
@Bean(name = "myFlux")
@ConditionalOnProperty(value = "spring.sleuth.function.type", havingValue = "MANUAL")
public Function<Flux<String>, Flux<String>> manualFunction() {
System.out.println("manual function");
return new SleuthManualFunction();
}
}
class SimpleFunction implements Function<String, String> {
private static final Logger log = LoggerFactory.getLogger(SimpleFunction.class);
@Override
public String apply(String input) {
log.info("Hello from simple [{}]", input);
return input.toUpperCase();
}
}
class SimpleReactiveFunction implements Function<Flux<String>, Flux<String>> {
private static final Logger log = LoggerFactory.getLogger(SimpleReactiveFunction.class);
@Override
public Flux<String> apply(Flux<String> input) {
return input.doOnNext(s -> log.info("Hello from simple [{}]", s)).map(String::toUpperCase);
}
}
class SimpleManualFunction implements Function<Message<String>, Message<String>> {
private static final Logger log = LoggerFactory.getLogger(SimpleFunction.class);
private final Tracing tracing;
SimpleManualFunction(Tracing tracing) {
this.tracing = tracing;
}
@Override
public Message<String> apply(Message<String> input) {
return (MessagingSleuthOperator.asFunction(this.tracing, input)
.andThen(msg -> MessagingSleuthOperator.withSpanInScope(this.tracing, msg, stringMessage -> {
log.info("Hello from simple manual [{}]", stringMessage.getPayload());
return stringMessage;
})).andThen(msg -> MessagingSleuthOperator.afterMessageHandled(this.tracing, msg, null))
.andThen(msg -> MessagingSleuthOperator.handleOutputMessage(this.tracing, msg))
.andThen(msg -> MessageBuilder.createMessage(msg.getPayload().toUpperCase(), msg.getHeaders()))
.andThen(msg -> MessagingSleuthOperator.afterMessageHandled(this.tracing, msg, null)).apply(input));
}
}
class SimpleReactiveManualFunction implements Function<Flux<Message<String>>, Flux<Message<String>>> {
private static final Logger log = LoggerFactory.getLogger(SimpleReactiveFunction.class);
private final Tracing tracing;
SimpleReactiveManualFunction(Tracing tracing) {
this.tracing = tracing;
}
@Override
public Flux<Message<String>> apply(Flux<Message<String>> input) {
return input
.map(message -> (MessagingSleuthOperator.asFunction(this.tracing, message))
.andThen(msg -> MessagingSleuthOperator.withSpanInScope(this.tracing, msg, stringMessage -> {
log.info("Hello from simple manual [{}]", stringMessage.getPayload());
return stringMessage;
})).andThen(msg -> MessagingSleuthOperator.afterMessageHandled(this.tracing, msg, null))
.andThen(msg -> MessageBuilder.createMessage(msg.getPayload().toUpperCase(), msg.getHeaders()))
.andThen(msg -> MessagingSleuthOperator.handleOutputMessage(this.tracing, msg))
.apply(message));
}
}
class SleuthNonReactiveFunction implements Function<String, String> {
private static final Logger log = LoggerFactory.getLogger(SleuthNonReactiveFunction.class);
private final ExecutorService executorService;
SleuthNonReactiveFunction(ExecutorService executorService) {
this.executorService = executorService;
}
@Override
public String apply(String input) {
log.info("Got a message");
try {
return this.executorService.submit(() -> {
log.info("Logging [{}] from a new thread", input);
return input.toUpperCase();
}).get(20, TimeUnit.MILLISECONDS);
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
class SleuthFunction implements Function<Flux<String>, Flux<String>> {
private static final Logger log = LoggerFactory.getLogger(SleuthFunction.class);
@Override
public Flux<String> apply(Flux<String> input) {
return input.doOnEach(signal -> log.info("Got a message"))
.flatMap(s -> Mono.delay(Duration.ofMillis(1), Schedulers.newParallel("foo")).map(aLong -> {
log.info("Logging [{}] from flat map", s);
return s.toUpperCase();
}));
}
}
class SleuthManualFunction implements Function<Flux<String>, Flux<String>> {
private static final Logger log = LoggerFactory.getLogger(SleuthManualFunction.class);
@Override
public Flux<String> apply(Flux<String> input) {
return input.doOnEach(WebFluxSleuthOperators.withSpanInScope(() -> log.info("Got a message"))).flatMap(s -> Mono
.subscriberContext().delayElement(Duration.ofMillis(1), Schedulers.newParallel("foo")).map(ctx -> {
WebFluxSleuthOperators.withSpanInScope(ctx, () -> log.info("Logging [{}] from flat map", s));
return s.toUpperCase();
})).doOnEach(signal -> {
WebFluxSleuthOperators.withSpanInScope(signal.getContext(), () -> log.info("Doing assertions"));
TraceContext traceContext = signal.getContext().get(TraceContext.class);
Assert.notNull(traceContext, "Context must be set by Sleuth instrumentation");
Assert.state(traceContext.traceIdString().equals("4883117762eb9420"), "TraceId must be propagated");
log.info("Assertions passed");
});
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -16,13 +16,18 @@
package org.springframework.cloud.sleuth.benchmarks.app.webflux;
import java.time.Duration;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import brave.sampler.Sampler;
import brave.handler.SpanHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import brave.propagation.TraceContext;
import brave.sampler.Sampler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.WebApplicationType;
@@ -31,9 +36,12 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.reactive.context.ReactiveWebServerInitializedEvent;
import org.springframework.cloud.sleuth.instrument.web.SkipPatternProvider;
import org.springframework.cloud.sleuth.instrument.web.WebFluxSleuthOperators;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.util.Assert;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -42,17 +50,18 @@ import org.springframework.web.bind.annotation.RestController;
*/
@SpringBootApplication
@RestController
public class SleuthBenchmarkingSpringWebFluxApp
implements ApplicationListener<ReactiveWebServerInitializedEvent> {
public class SleuthBenchmarkingSpringWebFluxApp implements ApplicationListener<ReactiveWebServerInitializedEvent> {
private static final Log log = LogFactory
.getLog(SleuthBenchmarkingSpringWebFluxApp.class);
private static final Logger log = LoggerFactory.getLogger(SleuthBenchmarkingSpringWebFluxApp.class);
/**
* Port to set.
*/
public int port;
public static void main(String... args) {
new SpringApplicationBuilder(SleuthBenchmarkingSpringWebFluxApp.class)
.web(WebApplicationType.REACTIVE).application().run(args);
new SpringApplicationBuilder(SleuthBenchmarkingSpringWebFluxApp.class).web(WebApplicationType.REACTIVE)
.application().run(args);
}
@RequestMapping("/foo")
@@ -71,11 +80,9 @@ public class SleuthBenchmarkingSpringWebFluxApp
}
@Bean
NettyReactiveWebServerFactory nettyReactiveWebServerFactory(
@Value("${server.port:0}") int serverPort) {
NettyReactiveWebServerFactory nettyReactiveWebServerFactory(@Value("${server.port:0}") int serverPort) {
log.info("Starting container at port [" + serverPort + "]");
return new NettyReactiveWebServerFactory(
serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort);
return new NettyReactiveWebServerFactory(serverPort == 0 ? SocketUtils.findAvailableTcpPort() : serverPort);
}
@Bean
@@ -90,4 +97,54 @@ public class SleuthBenchmarkingSpringWebFluxApp
this.port = event.getWebServer().getPort();
}
@GetMapping("/simple")
public Mono<String> simple() {
return Mono.just("hello").map(String::toUpperCase);
}
@GetMapping("/complexNoSleuth")
public Mono<String> complexNoSleuth() {
return Flux.range(1, 10).map(String::valueOf).collect(Collectors.toList())
.doOnEach(signal -> log.info("Got a request"))
.flatMap(s -> Mono.delay(Duration.ofMillis(1), Schedulers.newParallel("foo")).map(aLong -> {
log.info("Logging [{}] from flat map", s);
return "";
}));
}
@GetMapping("/complex")
public Mono<String> complex() {
return Flux.range(1, 10).map(String::valueOf).collect(Collectors.toList())
.doOnEach(signal -> log.info("Got a request"))
.flatMap(s -> Mono.delay(Duration.ofMillis(1), Schedulers.newParallel("foo")).map(aLong -> {
log.info("Logging [{}] from flat map", s);
return "";
})).doOnEach(signal -> {
log.info("Doing assertions");
TraceContext traceContext = signal.getContext().get(TraceContext.class);
Assert.notNull(traceContext, "Context must be set by Sleuth instrumentation");
Assert.state(traceContext.traceIdString().equals("4883117762eb9420"), "TraceId must be propagated");
log.info("Assertions passed");
});
}
@GetMapping("/complexManual")
public Mono<String> complexManual() {
return Flux.range(1, 10).map(String::valueOf).collect(Collectors.toList())
.doOnEach(WebFluxSleuthOperators.withSpanInScope(() -> log.info("Got a request")))
.flatMap(s -> Mono.subscriberContext().delayElement(Duration.ofMillis(1), Schedulers.newParallel("foo"))
.map(ctx -> {
WebFluxSleuthOperators.withSpanInScope(ctx,
() -> log.info("Logging [{}] from flat map", s));
return "";
}))
.doOnEach(signal -> {
WebFluxSleuthOperators.withSpanInScope(signal.getContext(), () -> log.info("Doing assertions"));
TraceContext traceContext = signal.getContext().get(TraceContext.class);
Assert.notNull(traceContext, "Context must be set by Sleuth instrumentation");
Assert.state(traceContext.traceIdString().equals("4883117762eb9420"), "TraceId must be propagated");
log.info("Assertions passed");
});
}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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;
public class SpringWebFluxOnLastBenchmark extends SpringWebFluxBenchmarks {
@Override
protected String[] runArgs() {
return new String[] { "--spring.jmx.enabled=false",
"--spring.application.name=defaultTraceContextWithOnLastOperator",
"--spring.sleuth.enabled=true",
"--spring.sleuth.reactor.on-each-operator=false" };
}
}

View File

@@ -1,3 +1,4 @@
logging.level:
org.springframework: ERROR
org.springframework.cloud.sleuth.benchmarks: INFO
org.springframework.sleuth: ERROR
brave: ERROR

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh;
import java.io.BufferedReader;
import java.io.File;
@@ -57,15 +57,13 @@ public class ProcessLauncherState {
this.args.add(count++, "-Djava.security.egd=file:/dev/./urandom");
this.args.add(count++, "-XX:TieredStopAtLevel=1"); // zoom
if (System.getProperty("bench.args") != null) {
this.args.addAll(count++,
Arrays.asList(System.getProperty("bench.args").split(" ")));
this.args.addAll(count++, Arrays.asList(System.getProperty("bench.args").split(" ")));
}
this.length = args.length;
this.home = new File(dir);
}
protected static String output(InputStream inputStream, String marker)
throws IOException {
protected static String output(InputStream inputStream, String marker) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader br = null;
br = new BufferedReader(new InputStreamReader(inputStream));
@@ -100,8 +98,7 @@ public class ProcessLauncherState {
public void after() throws Exception {
if (started != null && started.isAlive()) {
System.err.println(
"Stopped " + mainClass + ": " + started.destroyForcibly().waitFor());
System.err.println("Stopped " + mainClass + ": " + started.destroyForcibly().waitFor());
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2013-2019 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
*
* https://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();
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2013-2020 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
*
* https://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 java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import brave.Tracing;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.TearDown;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.sleuth.benchmarks.app.stream.SleuthBenchmarkingStreamApplication;
import org.springframework.cloud.stream.binder.test.InputDestination;
import org.springframework.cloud.stream.binder.test.OutputDestination;
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@Disabled
public class SampleTests {
@Test
public void testStream() throws Exception {
for (BenchmarkContext.Instrumentation value : BenchmarkContext.Instrumentation.values()) {
run(value);
}
// run(BenchmarkContext.Instrumentation.sleuthReactiveSimpleManual);
}
private void run(BenchmarkContext.Instrumentation value) throws Exception {
BenchmarkContext context = new BenchmarkContext();
System.out.println("\n\n\n\n WILL WORK WITH [" + value + "]\n\n\n\n");
context.instrumentation = value;
context.setup();
try {
context.run(value);
}
finally {
context.clean();
}
System.out.println("\n\n FINISHED WITH [" + value + "]\n\n\n\n");
}
public static class BenchmarkContext {
volatile ConfigurableApplicationContext applicationContext;
volatile InputDestination input;
volatile OutputDestination output;
@Param
private Instrumentation instrumentation;
@Setup
public void setup() {
this.applicationContext = initContext();
this.input = this.applicationContext.getBean(InputDestination.class);
this.output = this.applicationContext.getBean(OutputDestination.class);
}
protected ConfigurableApplicationContext initContext() {
SpringApplication application = new SpringApplicationBuilder(SleuthBenchmarkingStreamApplication.class)
.web(WebApplicationType.REACTIVE).application();
return application.run(runArgs());
}
protected String[] runArgs() {
List<String> strings = new ArrayList<>();
strings.addAll(Arrays.asList("--spring.jmx.enabled=false",
"--spring.application.name=defaultTraceContextForStream" + instrumentation.name()));
strings.addAll(instrumentation.entires.stream().map(s -> "--" + s).collect(Collectors.toList()));
return strings.toArray(new String[0]);
}
void run(Instrumentation value) {
System.out.println("Sending the message to input");
input.send(MessageBuilder.withPayload("hello".getBytes())
.setHeader("b3", "4883117762eb9420-4883117762eb9420-1").build());
System.out.println("Retrieving the message for tests");
Message<byte[]> message = output.receive(200L);
System.out.println("Got the message from output");
assertThat(message).isNotNull();
System.out.println("Message is not null");
assertThat(message.getPayload()).isEqualTo("HELLO".getBytes());
System.out.println("Payload is HELLO");
if (!value.toString().toLowerCase().contains("nosleuth")) {
String b3 = message.getHeaders().get("b3", String.class);
System.out.println("Checking the b3 header [" + b3 + "]");
assertThat(b3).startsWith("4883117762eb9420");
}
}
@TearDown
public void clean() throws Exception {
Tracing current = Tracing.current();
if (current != null) {
current.close();
}
try {
this.applicationContext.close();
}
catch (Exception ig) {
}
}
public enum Instrumentation {
noSleuthSimple("spring.sleuth.enabled=false,spring.sleuth.function.type=simple");
private Set<String> entires = new HashSet<>();
Instrumentation(String key, String value) {
this.entires.add(key + "=" + value);
}
Instrumentation(String commaSeparated) {
this.entires.addAll(StringUtils.commaDelimitedListToSet(commaSeparated));
}
}
}
@Configuration
@Import(TestChannelBinderConfiguration.class)
static class TestConfiguration {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.mvc;
import java.util.concurrent.TimeUnit;
import jmh.mbr.junit5.Microbenchmark;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -37,13 +38,18 @@ import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.BDDAssertions.then;
@Measurement(iterations = 5)
@Warmup(iterations = 10)
@Fork(3)
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 10, time = 1)
@Fork(2)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(Threads.MAX)
public class AnnotationBenchmarks {
@Microbenchmark
public class AnnotationBenchmarksTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:annotation.csv");
}
@Benchmark
public void manuallyCreatedSpans(BenchmarkContext context) throws Exception {
@@ -64,9 +70,8 @@ public class AnnotationBenchmarks {
@Setup
public void setup() {
this.withSleuth = new SpringApplication(SleuthBenchmarkingSpringApp.class)
.run("--spring.jmx.enabled=false",
"--spring.application.name=withSleuth");
this.withSleuth = new SpringApplication(SleuthBenchmarkingSpringApp.class).run("--spring.jmx.enabled=false",
"--spring.application.name=withSleuth");
this.sleuth = this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -14,10 +14,11 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.mvc;
import java.util.concurrent.TimeUnit;
import jmh.mbr.junit5.Microbenchmark;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -37,13 +38,18 @@ import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.BDDAssertions.then;
@Measurement(iterations = 5)
@Warmup(iterations = 10)
@Fork(3)
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 10, time = 1)
@Fork(2)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(Threads.MAX)
public class AsyncBenchmarks {
@Microbenchmark
public class AsyncBenchmarksTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:async.csv");
}
@Benchmark
public void asyncMethodWithoutSleuth(BenchmarkContext context) throws Exception {
@@ -68,18 +74,13 @@ public class AsyncBenchmarks {
@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);
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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.mvc;
import java.io.IOException;
import java.util.concurrent.Callable;
@@ -28,6 +28,7 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import brave.servlet.TracingFilter;
import jmh.mbr.junit5.Microbenchmark;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -62,17 +63,21 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Warmup(iterations = 10)
@Warmup(iterations = 5)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(Threads.MAX)
public class HttpFilterBenchmarks {
@Microbenchmark
public class HttpFilterBenchmarksTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:http_filter.csv");
}
@Benchmark
@Measurement(iterations = 5, time = 1)
@Fork(3)
public void filterWithoutSleuth(BenchmarkContext context)
throws IOException, ServletException {
@Fork(2)
public void filterWithoutSleuth(BenchmarkContext context) throws IOException, ServletException {
MockHttpServletRequest request = builder().buildRequest(new MockServletContext());
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
@@ -82,9 +87,8 @@ public class HttpFilterBenchmarks {
@Benchmark
@Measurement(iterations = 5, time = 1)
@Fork(3)
public void filterWithSleuth(BenchmarkContext context)
throws ServletException, IOException {
@Fork(2)
public void filterWithSleuth(BenchmarkContext context) throws ServletException, IOException {
MockHttpServletRequest request = builder().buildRequest(new MockServletContext());
MockHttpServletResponse response = new MockHttpServletResponse();
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
@@ -107,12 +111,10 @@ public class HttpFilterBenchmarks {
}
private MockHttpServletRequestBuilder builder() {
return get("/").accept(MediaType.APPLICATION_JSON).header("User-Agent",
"MockMvc");
return get("/").accept(MediaType.APPLICATION_JSON).header("User-Agent", "MockMvc");
}
private void performRequest(MockMvc mockMvc, String url, String expectedResult)
throws Exception {
private void performRequest(MockMvc mockMvc, String url, String expectedResult) throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/" + url)).andExpect(status().isOk())
.andExpect(request().asyncStarted()).andReturn();
@@ -135,16 +137,12 @@ public class HttpFilterBenchmarks {
@Setup
public void setup() {
this.withSleuth = new SpringApplication(SleuthBenchmarkingSpringApp.class)
.run("--spring.jmx.enabled=false",
"--spring.application.name=withSleuth");
this.withSleuth = new SpringApplication(SleuthBenchmarkingSpringApp.class).run("--spring.jmx.enabled=false",
"--spring.application.name=withSleuth");
this.tracingFilter = this.withSleuth.getBean(TracingFilter.class);
this.mockMvcForTracedController = MockMvcBuilders
.standaloneSetup(
this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class))
.build();
this.mockMvcForUntracedController = MockMvcBuilders
.standaloneSetup(new VanillaController()).build();
.standaloneSetup(this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class)).build();
this.mockMvcForUntracedController = MockMvcBuilders.standaloneSetup(new VanillaController()).build();
}
@TearDown
@@ -162,8 +160,8 @@ public class HttpFilterBenchmarks {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.mvc;
import java.io.IOException;
import java.util.Collections;
@@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import brave.spring.web.TracingClientHttpRequestInterceptor;
import jmh.mbr.junit5.Microbenchmark;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -49,24 +50,26 @@ 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)
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 10, time = 1)
@Fork(2)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(Threads.MAX)
public class RestTemplateBenchmark {
@Microbenchmark
public class RestTemplateBenchmarkTests {
@Benchmark
public void syncEndpointWithoutSleuth(BenchmarkContext context)
throws IOException, ServletException {
then(context.untracedTemplate.getForObject("/foo", String.class))
.isEqualTo("foo");
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:rest_template.csv");
}
@Benchmark
public void syncEndpointWithSleuth(BenchmarkContext context)
throws ServletException, IOException {
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");
}
@@ -83,18 +86,14 @@ public class RestTemplateBenchmark {
@Setup
public void setup() {
new SpringApplication(SleuthBenchmarkingSpringApp.class).run(
"--spring.jmx.enabled=false", "--spring.application.name=withSleuth");
this.mockMvc = MockMvcBuilders
.standaloneSetup(
this.withSleuth.getBean(SleuthBenchmarkingSpringApp.class))
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(TracingClientHttpRequestInterceptor.class)));
this.untracedTemplate = new RestTemplate(
new MockMvcClientHttpRequestFactory(this.mockMvc));
this.tracedTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(this.mockMvc));
this.tracedTemplate.setInterceptors(
Collections.singletonList(this.withSleuth.getBean(TracingClientHttpRequestInterceptor.class)));
this.untracedTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(this.mockMvc));
}
@TearDown

View File

@@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.mvc;
import jmh.mbr.junit5.Microbenchmark;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -27,11 +28,18 @@ import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;
import org.springframework.cloud.sleuth.benchmarks.jmh.ProcessLauncherState;
@Measurement(iterations = 5)
@Warmup(iterations = 1)
@Fork(value = 2, warmups = 0)
@BenchmarkMode(Mode.AverageTime)
public class StartupBenchmark {
@Microbenchmark
public class StartupBenchmarkTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:startup.csv");
}
@Benchmark
public void withAnnotations(ApplicationState state) throws Exception {
@@ -46,25 +54,21 @@ public class StartupBenchmark {
@Benchmark
public void withoutAsync(ApplicationState state) throws Exception {
state.setExtraArgs("--spring.sleuth.async.enabled=false",
"--spring.sleuth.annotation.enabled=false");
state.setExtraArgs("--spring.sleuth.async.enabled=false", "--spring.sleuth.annotation.enabled=false");
state.run();
}
@Benchmark
public void withoutScheduled(ApplicationState state) throws Exception {
state.setExtraArgs("--spring.sleuth.scheduled.enabled=false",
"--spring.sleuth.async.enabled=false",
state.setExtraArgs("--spring.sleuth.scheduled.enabled=false", "--spring.sleuth.async.enabled=false",
"--spring.sleuth.annotation.enabled=false");
state.run();
}
@Benchmark
public void withoutWeb(ApplicationState state) throws Exception {
state.setExtraArgs("--spring.sleuth.web.enabled=false",
"--spring.sleuth.scheduled.enabled=false",
"--spring.sleuth.async.enabled=false",
"--spring.sleuth.annotation.enabled=false");
state.setExtraArgs("--spring.sleuth.web.enabled=false", "--spring.sleuth.scheduled.enabled=false",
"--spring.sleuth.async.enabled=false", "--spring.sleuth.annotation.enabled=false");
state.run();
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2013-2020 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
*
* https://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.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import brave.Tracing;
import jmh.mbr.junit5.Microbenchmark;
import org.junit.platform.commons.annotation.Testable;
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.Param;
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.Warmup;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.sleuth.benchmarks.app.stream.SleuthBenchmarkingStreamApplication;
import org.springframework.cloud.stream.binder.test.InputDestination;
import org.springframework.cloud.stream.binder.test.OutputDestination;
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 10, time = 1)
@Fork(2)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Microbenchmark
public class MicroBenchmarkStreamTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:stream.csv");
}
@Benchmark
@Testable
public void testStream(BenchmarkContext context) throws Exception {
context.run();
}
@State(Scope.Benchmark)
public static class BenchmarkContext {
volatile ConfigurableApplicationContext applicationContext;
volatile InputDestination input;
volatile OutputDestination output;
@Param
private Instrumentation instrumentation;
@Setup
public void setup() {
this.applicationContext = initContext();
this.input = this.applicationContext.getBean(InputDestination.class);
this.output = this.applicationContext.getBean(OutputDestination.class);
}
protected ConfigurableApplicationContext initContext() {
SpringApplication application = new SpringApplicationBuilder(SleuthBenchmarkingStreamApplication.class)
.web(WebApplicationType.REACTIVE).application();
return application.run(runArgs());
}
protected String[] runArgs() {
List<String> strings = new ArrayList<>();
strings.addAll(Arrays.asList("--spring.jmx.enabled=false",
"--spring.application.name=defaultTraceContextForStream" + instrumentation.name()));
strings.addAll(instrumentation.entires.stream().map(s -> "--" + s).collect(Collectors.toList()));
return strings.toArray(new String[0]);
}
void run() {
// System.out.println("Sending the message to input");
input.send(MessageBuilder.withPayload("hello".getBytes())
.setHeader("b3", "4883117762eb9420-4883117762eb9420-1").build());
// System.out.println("Retrieving the message for tests");
Message<byte[]> message = output.receive(200L);
// System.out.println("Got the message from output");
assertThat(message).isNotNull();
// System.out.println("Message is not null");
assertThat(message.getPayload()).isEqualTo("HELLO".getBytes());
// System.out.println("Payload is HELLO");
if (!instrumentation.toString().toLowerCase().contains("nosleuth")) {
String b3 = message.getHeaders().get("b3", String.class);
// System.out.println("Checking the b3 header [" + b3 + "]");
assertThat(b3).startsWith("4883117762eb9420");
}
}
@TearDown
public void clean() throws Exception {
Tracing current = Tracing.current();
if (current != null) {
current.close();
}
try {
this.applicationContext.close();
}
catch (Exception ig) {
}
}
public enum Instrumentation {
noSleuthSimple("spring.sleuth.enabled=false,spring.sleuth.function.type=simple"), sleuthSimple(
"spring.sleuth.reactor.instrumentation-type=MANUAL,spring.sleuth.function.type=simple"), noSleuthReactiveSimple(
"spring.sleuth.enabled=false,spring.sleuth.function.type=reactive_simple"), sleuthReactiveSimpleOnEach(
"spring.sleuth.reactor.instrumentation-type=DECORATE_ON_EACH,spring.sleuth.integration.enabled=true,spring.sleuth.function.type=DECORATE_ON_EACH"),
// This won't work with messaging
// sleuthReactiveSimpleOnLast("spring.sleuth.reactor.instrumentation-type=DECORATE_ON_LAST,spring.sleuth.function.type=DECORATE_ON_LAST"),
// NO FUNCTION, NO INTEGRATION, MANUAL OPERATORS
sleuthSimpleManual(
"spring.sleuth.function.enabled=false,spring.sleuth.integration.enabled=false,spring.sleuth.function.type=simple_manual"), sleuthReactiveSimpleManual(
"spring.sleuth.function.enabled=false,spring.sleuth.integration.enabled=false,spring.sleuth.function.type=reactive_simple_manual"),
// NO FUNCTION - OLD INTEGRATION STYLE
sleuthSimpleNoFunctionInstrumentationManual(
"spring.sleuth.function.type=simple_manual,spring.sleuth.function.enabled=false,spring.sleuth.integration.enabled=true,spring.sleuth.reactor.instrumentation-type=MANUAL"), sleuthReactiveSimpleNoFunctionInstrumentationManual(
"spring.sleuth.function.type=reactive_simple_manual,spring.sleuth.function.enabled=false,spring.sleuth.integration.enabled=true,spring.sleuth.reactor.instrumentation-type=MANUAL");
private Set<String> entires = new HashSet<>();
Instrumentation(String key, String value) {
this.entires.add(key + "=" + value);
}
Instrumentation(String commaSeparated) {
this.entires.addAll(StringUtils.commaDelimitedListToSet(commaSeparated));
}
}
}
@Configuration
@Import(TestChannelBinderConfiguration.class)
static class TestConfiguration {
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2013-2020 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
*
* https://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.webflux;
import java.util.concurrent.TimeUnit;
import brave.Tracing;
import jmh.mbr.junit5.Microbenchmark;
import org.junit.platform.commons.annotation.Testable;
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.Param;
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.Warmup;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.sleuth.benchmarks.app.webflux.SleuthBenchmarkingSpringWebFluxApp;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.web.reactive.server.WebTestClient;
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 10, time = 1)
@Fork(2)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Microbenchmark
public class MicroBenchmarkHttpTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:http.csv");
}
@Benchmark
@Testable
public void test(BenchmarkContext context) throws Exception {
context.run();
}
@State(Scope.Benchmark)
public static class BenchmarkContext {
volatile ConfigurableApplicationContext applicationContext;
volatile WebTestClient webTestClient;
@Param
private Instrumentation instrumentation;
@Setup
public void setup() {
this.applicationContext = initContext();
this.webTestClient = WebTestClient.bindToApplicationContext(applicationContext).build();
}
protected ConfigurableApplicationContext initContext() {
SpringApplication application = new SpringApplicationBuilder(SleuthBenchmarkingSpringWebFluxApp.class)
.web(WebApplicationType.REACTIVE).application();
return application.run(runArgs());
}
protected String[] runArgs() {
return new String[] { "--spring.jmx.enabled=false",
"--spring.application.name=defaultTraceContext" + instrumentation.name(),
"--" + instrumentation.key + "=" + instrumentation.value };
}
void run() {
this.webTestClient.get().uri(this.instrumentation.url).header("X-B3-TraceId", "4883117762eb9420")
.header("X-B3-SpanId", "4883117762eb9420").exchange().expectStatus().isOk();
}
@TearDown
public void clean() throws Exception {
Tracing current = Tracing.current();
if (current != null) {
current.close();
}
try {
this.applicationContext.close();
}
catch (Exception ig) {
}
}
public enum Instrumentation {
noSleuthSimple("spring.sleuth.enabled", "false", "/simple"), sleuthSimpleManual(
"spring.sleuth.reactor.instrumentation-type", "MANUAL",
"/simple"), noSleuthComplex("spring.sleuth.enabled", "false", "/complexNoSleuth"), onEachComplex(
"spring.sleuth.reactor.instrumentation-type", "DECORATE_ON_EACH",
"/complex"), onLastComplex("spring.sleuth.reactor.instrumentation-type", "DECORATE_ON_LAST",
"/complex"), onManualComplex("spring.sleuth.reactor.instrumentation-type", "MANUAL",
"/complexManual");
private String key;
private String value;
private String url;
Instrumentation(String key, String value, String url) {
this.key = key;
this.value = value;
this.url = url;
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.webflux;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@@ -26,6 +26,7 @@ import brave.httpclient.TracingHttpClientBuilder;
import brave.propagation.CurrentTraceContext;
import brave.propagation.TraceContext;
import brave.sampler.Sampler;
import jmh.mbr.junit5.Microbenchmark;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@@ -55,28 +56,38 @@ import org.springframework.context.ConfigurableApplicationContext;
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 10, time = 1)
@Fork(3)
@Fork(2)
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(2)
@State(Scope.Benchmark)
public class SpringWebFluxBenchmarks {
@Microbenchmark
public class SpringWebFluxBenchmarksTests {
static final SpanHandler FAKE_SPAN_HANDLER = new SpanHandler() {
// intentionally anonymous to prevent logging fallback on NOOP
};
protected static TraceContext defaultTraceContext = TraceContext.newBuilder().traceIdHigh(333L).traceId(444L)
.spanId(3).sampled(true).build();
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:webflux.csv");
}
protected static TraceContext defaultTraceContext = TraceContext.newBuilder()
.traceIdHigh(333L).traceId(444L).spanId(3).sampled(true).build();
protected ConfigurableApplicationContext applicationContext;
protected SleuthBenchmarkingSpringWebFluxApp springWebFluxApp;
CloseableHttpClient client;
CloseableHttpClient tracedClient;
CloseableHttpClient unsampledClient;
private String baseUrl;
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(".*" + SpringWebFluxBenchmarks.class.getSimpleName() + ".*")
Options opt = new OptionsBuilder().include(".*" + SpringWebFluxBenchmarksTests.class.getSimpleName() + ".*")
.build();
new Runner(opt).run();
@@ -87,8 +98,7 @@ public class SpringWebFluxBenchmarks {
}
protected CloseableHttpClient newClient(HttpTracing httpTracing) {
return TracingHttpClientBuilder.create(httpTracing).disableAutomaticRetries()
.build();
return TracingHttpClientBuilder.create(httpTracing).disableAutomaticRetries().build();
}
protected CloseableHttpClient newClient() {
@@ -107,21 +117,18 @@ public class SpringWebFluxBenchmarks {
public void setup() {
ConfigurableApplicationContext context = initContext();
this.applicationContext = context;
this.springWebFluxApp = this.applicationContext
.getBean(SleuthBenchmarkingSpringWebFluxApp.class);
this.springWebFluxApp = this.applicationContext.getBean(SleuthBenchmarkingSpringWebFluxApp.class);
baseUrl = "http://127.0.0.1:" + springWebFluxApp.port + "/foo";
client = newClient();
tracedClient = newClient(HttpTracing
.create(Tracing.newBuilder().addSpanHandler(FAKE_SPAN_HANDLER).build()));
unsampledClient = newClient(HttpTracing.create(Tracing.newBuilder()
.sampler(Sampler.NEVER_SAMPLE).addSpanHandler(FAKE_SPAN_HANDLER).build()));
tracedClient = newClient(HttpTracing.create(Tracing.newBuilder().addSpanHandler(FAKE_SPAN_HANDLER).build()));
unsampledClient = newClient(HttpTracing
.create(Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).addSpanHandler(FAKE_SPAN_HANDLER).build()));
postSetUp();
}
protected ConfigurableApplicationContext initContext() {
SpringApplication application = new SpringApplicationBuilder(
SleuthBenchmarkingSpringWebFluxApp.class).web(WebApplicationType.REACTIVE)
.application();
SpringApplication application = new SpringApplicationBuilder(SleuthBenchmarkingSpringWebFluxApp.class)
.web(WebApplicationType.REACTIVE).application();
customSpringApplication(application);
return application.run(runArgs());
}
@@ -134,8 +141,7 @@ public class SpringWebFluxBenchmarks {
}
protected String[] runArgs() {
return new String[] { "--spring.jmx.enabled=false",
"--spring.application.name=defaultTraceContext",
return new String[] { "--spring.jmx.enabled=false", "--spring.application.name=defaultTraceContext",
"--spring.sleuth.enabled=true" };
}
@@ -171,8 +177,7 @@ public class SpringWebFluxBenchmarks {
@Benchmark
public void tracedClient_get_resumeTrace() throws Exception {
try (CurrentTraceContext.Scope scope = Tracing.current().currentTraceContext()
.newScope(defaultTraceContext)) {
try (CurrentTraceContext.Scope scope = Tracing.current().currentTraceContext().newScope(defaultTraceContext)) {
get(tracedClient);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.webflux;
import jmh.mbr.junit5.Microbenchmark;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
@@ -24,21 +25,23 @@ import org.openjdk.jmh.runner.options.OptionsBuilder;
/**
* @author alvin
*/
public class WithOutReactorSleuthSpringWebFluxBenchmarks extends SpringWebFluxBenchmarks {
@Microbenchmark
public class WithOutReactorSleuthSpringWebFluxBenchmarksTests extends SpringWebFluxBenchmarksTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:webflux_no_reactor_instrumentation.csv");
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(
".*" + WithOutReactorSleuthSpringWebFluxBenchmarks.class.getSimpleName()
+ ".*")
.build();
Options opt = new OptionsBuilder()
.include(".*" + WithOutReactorSleuthSpringWebFluxBenchmarksTests.class.getSimpleName() + ".*").build();
new Runner(opt).run();
}
@Override
protected String[] runArgs() {
return new String[] { "--spring.jmx.enabled=false",
"--spring.application.name=defaultTraceContext",
return new String[] { "--spring.jmx.enabled=false", "--spring.application.name=defaultTraceContext",
"--spring.sleuth.enabled=true", "--spring.sleuth.reactor.enabled=false"
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.benchmarks.jmh.benchmarks;
package org.springframework.cloud.sleuth.benchmarks.jmh.webflux;
import jmh.mbr.junit5.Microbenchmark;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
@@ -24,20 +25,23 @@ import org.openjdk.jmh.runner.options.OptionsBuilder;
/**
* @author alvin
*/
public class WithOutSleuthSpringWebFluxBenchmarks extends SpringWebFluxBenchmarks {
@Microbenchmark
public class WithOutSleuthSpringWebFluxBenchmarksTests extends SpringWebFluxBenchmarksTests {
static {
System.setProperty("jmh.mbr.report.publishTo", "csv:webflux_no_sleuth_instrumentation.csv");
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(
".*" + WithOutSleuthSpringWebFluxBenchmarks.class.getSimpleName() + ".*")
.build();
Options opt = new OptionsBuilder()
.include(".*" + WithOutSleuthSpringWebFluxBenchmarksTests.class.getSimpleName() + ".*").build();
new Runner(opt).run();
}
@Override
protected String[] runArgs() {
return new String[] { "--spring.jmx.enabled=false",
"--spring.application.name=defaultTraceContext",
return new String[] { "--spring.jmx.enabled=false", "--spring.application.name=defaultTraceContext",
"--spring.sleuth.enabled=false" };
}

16
pom.xml
View File

@@ -174,6 +174,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-dependencies</artifactId>
<version>${spring-cloud-function.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
@@ -181,6 +188,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!--<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>${reactor.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>-->
<!-- Spock -->
<dependency>
<groupId>org.spockframework</groupId>
@@ -241,9 +255,11 @@
<spring-cloud-gateway.version>3.0.0-SNAPSHOT</spring-cloud-gateway.version>
<spring-cloud-circuitbreaker.version>2.0.0-SNAPSHOT</spring-cloud-circuitbreaker.version>
<spring-cloud-stream.version>3.1.0-SNAPSHOT</spring-cloud-stream.version>
<spring-cloud-function.version>3.1.0-SNAPSHOT</spring-cloud-function.version>
<spring-cloud-netflix.version>3.0.0-SNAPSHOT</spring-cloud-netflix.version>
<spring-cloud-openfeign.version>3.0.0-SNAPSHOT</spring-cloud-openfeign.version>
<brave.version>5.12.3</brave.version>
<!-- <reactor.version>2020.0.0-SNAPSHOT</reactor.version>-->
<spring-security-boot-autoconfigure.version>2.3.1.RELEASE</spring-security-boot-autoconfigure.version>
<disable.nohttp.checks>false</disable.nohttp.checks>
<okhttp.version>3.14.6</okhttp.version>

View File

@@ -1,5 +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.* -rf csv -rff jmh-result.csv | tee target/benchmarks.log
./mvnw clean verify -Djmh.mbr.report.publishTo=csv:http.csv -pl benchmarks -Pbenchmarks
# java -Djmh.ignoreLock=true -jar benchmarks/target/benchmarks.jar org.springframework.cloud.sleuth.benchmarks.jmh.* -rf csv -rff jmh-result.csv | tee target/benchmarks.log

View File

@@ -85,6 +85,11 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
@@ -100,6 +105,11 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-context</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -221,7 +221,7 @@ class TraceBaggageConfiguration {
* {@link SpanHandler} beans, even if {@link SpanHandler#NOOP}, can trigger
* {@code org.springframework.cloud.sleuth.sampler.SamplerCondition}
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@Conditional(BaggageTagSpanHandlerCondition.class)
@EnableConfigurationProperties(SleuthBaggageProperties.class)
static class BaggageTagSpanHandlerConfiguration {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -0,0 +1,232 @@
/*
* Copyright 2013-2020 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
*
* https://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.instrument.messaging;
import java.util.function.Consumer;
import java.util.function.Function;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.TraceContextOrSamplingFlags;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.Message;
/**
* Messaging helpers to manually parse and inject spans. We're treating message headers as
* a context that gets passed through.
*
* The {@code forInputMessage} factory methods will retrieve the tracing context from the
* message headers and set up a a child span in the header under key
* {@link Span#getClass()} name. If you need to continue it or tag it, it's enough to
* retrieve it from the headers.
*
* The first messaging span (the one that was first found in the input message) is present
* under the {@code traceHandlerParentSpan} header key.
*
* When calling the {@code toOutputMessage} factory method, we will
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
public final class MessagingSleuthOperator {
private static final Log log = LogFactory.getLog(MessagingSleuthOperator.class);
private MessagingSleuthOperator() {
throw new IllegalStateException("You can't instantiate a utility class");
}
/**
* Executes a span wrapped operation for an input message.
* @param tracing - tracing bean
* @param message - message to wrap
* @param withSpanInScope - an operation that will be wrapped in a span and the span
* will be reported at the end
* @param <T> - type of payload
* @return message with tracing context
*/
public static <T> Message<T> forInputMessage(Tracing tracing, Message<T> message,
Consumer<Message<T>> withSpanInScope) {
TraceMessageHandler traceMessageHandler = TraceMessageHandler
.forNonSpringIntegration(tracing);
MessageAndSpans wrappedInputMessage = traceMessageHandler
.wrapInputMessage(message, "");
if (log.isDebugEnabled()) {
log.debug("Wrapped input msg " + wrappedInputMessage);
}
Tracer tracer = tracing.tracer();
Throwable t = null;
try (Tracer.SpanInScope ws = tracer
.withSpanInScope(wrappedInputMessage.childSpan.start())) {
withSpanInScope.accept(wrappedInputMessage.msg);
}
catch (Exception e) {
t = e;
throw e;
}
finally {
traceMessageHandler.afterMessageHandled(wrappedInputMessage.childSpan, t);
}
return wrappedInputMessage.msg;
}
/**
* Processes the input message and returns a message with a header containing a span.
* @param tracing - tracing bean
* @param message - input message to process
* @param <T> - payload type
* @return message with tracing context
*/
public static <T> Message<T> forInputMessage(Tracing tracing, Message<T> message) {
TraceMessageHandler traceMessageHandler = TraceMessageHandler
.forNonSpringIntegration(tracing);
MessageAndSpans wrappedInputMessage = traceMessageHandler
.wrapInputMessage(message, "");
if (log.isDebugEnabled()) {
log.debug("Wrapped input msg " + wrappedInputMessage);
}
return wrappedInputMessage.msg;
}
/**
* Function converting an input message to a message with tracing headers.
* @param tracing - tracing bean
* @param inputMessage - input message to process
* @param <T> input message type
* @return function representation of input message with tracing context
*/
public static <T> Function<Message<T>, Message<T>> asFunction(Tracing tracing,
Message<T> inputMessage) {
return stringMessage -> MessagingSleuthOperator.forInputMessage(tracing,
inputMessage);
}
/**
* Retrieves tracing information from message headers.
* @param tracing - tracing bean
* @param message - message to process
* @param <T> - payload type
* @return span retrieved from message or {@code null} if there was no span
*/
public static <T> Span spanFromMessage(Tracing tracing, Message<T> message) {
TraceMessageHandler traceMessageHandler = TraceMessageHandler
.forNonSpringIntegration(tracing);
Span span = traceMessageHandler.spanFromMessage(message);
if (log.isDebugEnabled()) {
log.debug("Found the following span in message " + span);
}
return span;
}
/**
* Retrieves tracing information from message headers and applies the operation.
* @param tracing - tracing bean
* @param message - message to process
* @param withSpanInScope - an operation that will be wrapped in a span but will not
* be reported
* @param <T> - payload type
*/
public static <T> void withSpanInScope(Tracing tracing, Message<T> message,
Consumer<Message<T>> withSpanInScope) {
Span span = spanFromMessage(tracing, message);
Tracer tracer = tracing.tracer();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
withSpanInScope.accept(message);
}
}
/**
* Retrieves tracing information from message headers and applies the operation.
* @param tracing - tracing bean
* @param message - message to process
* @param withSpanInScope - an operation that will be wrapped in a span but will not
* be reported
* @param <T> - payload type
* @return a message with tracing headers.
*/
public static <T> Message<T> withSpanInScope(Tracing tracing, Message<T> message,
Function<Message<T>, Message<T>> withSpanInScope) {
Span span = spanFromMessage(tracing, message);
Tracer tracer = tracing.tracer();
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
return withSpanInScope.apply(message);
}
}
/**
* Creates an output message with tracing headers and reports the corresponding
* producer span. If the message contains a header called {@code destination} it will
* be used to tag the span with destination name.
* @param tracing - tracing bean
* @param message - message to which tracing headers should be injected
* @param <T> - message payload
* @return instrumented message
*/
public static <T> Message<T> handleOutputMessage(Tracing tracing,
Message<T> message) {
return handleOutputMessage(tracing, message, null);
}
/**
* Creates an output message with tracing headers and reports the corresponding
* producer span. If the message contains a header called {@code destination} it will
* be used to tag the span with destination name.
* @param tracing - tracing bean
* @param message - message to which tracing headers should be injected
* @param throwable - exception that took place while processing the message
* @param <T> - message payload
* @return instrumented message
*/
public static <T> Message<T> handleOutputMessage(Tracing tracing, Message<T> message,
Throwable throwable) {
TraceMessageHandler traceMessageHandler = TraceMessageHandler
.forNonSpringIntegration(tracing);
Span span = traceMessageHandler.parentSpan(message);
span = span != null ? span : traceMessageHandler.consumerSpan(message);
if (span == null) {
log.warn(
"Can't find neither parent nor consumer span. Will return the message with no tracing header changes");
return message;
}
MessageAndSpan messageAndSpan = traceMessageHandler.wrapOutputMessage(message,
TraceContextOrSamplingFlags.create(span.context()),
String.valueOf(message.getHeaders().getOrDefault("destination", "")));
traceMessageHandler.afterMessageHandled(messageAndSpan.span, throwable);
return messageAndSpan.msg;
}
/**
* Reports the span stored in the message.
* @param tracing - tracing bean
* @param message - message with tracing context
* @param ex - potential exception that took place while processing
* @param <T> - message payload
* @return instrumented message
*/
public static <T> Message<T> afterMessageHandled(Tracing tracing, Message<T> message,
Throwable ex) {
TraceMessageHandler traceMessageHandler = TraceMessageHandler
.forNonSpringIntegration(tracing);
Span span = traceMessageHandler.spanFromMessage(message);
traceMessageHandler.afterMessageHandled(span, ex);
return message;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2013-2020 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
*
* https://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.instrument.messaging;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.TraceContextOrSamplingFlags;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.cloud.function.context.catalog.FunctionAroundWrapper;
import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} to enable tracing via Spring Cloud Function.
*
* @author Oleg Zhurakousky
* @author Marcin Grzejszczak
* @since 3.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.sleuth.function.enabled", matchIfMissing = true)
@ConditionalOnBean(Tracing.class)
@ConditionalOnClass({ Tracer.class, FunctionAroundWrapper.class })
@AutoConfigureAfter(TraceAutoConfiguration.class)
class TraceFunctionAutoConfiguration {
@Bean
TraceFunctionAroundWrapper traceFunctionAroundWrapper(Environment environment,
Tracing tracing) {
return new TraceFunctionAroundWrapper(environment, tracing);
}
}
class TraceFunctionAroundWrapper extends FunctionAroundWrapper
implements ApplicationListener<RefreshScopeRefreshedEvent> {
private static final Log log = LogFactory.getLog(TraceFunctionAroundWrapper.class);
private final Environment environment;
private final Tracing tracing;
final Map<String, String> functionToDestinationCache = new ConcurrentHashMap<>();
TraceFunctionAroundWrapper(Environment environment, Tracing tracing) {
this.environment = environment;
this.tracing = tracing;
}
@Override
protected Object doApply(Message<byte[]> message,
SimpleFunctionRegistry.FunctionInvocationWrapper targetFunction) {
TraceMessageHandler traceMessageHandler = TraceMessageHandler
.forNonSpringIntegration(this.tracing);
if (log.isDebugEnabled()) {
log.debug("Will retrieve the tracing headers from the message");
}
MessageAndSpans wrappedInputMessage = traceMessageHandler
.wrapInputMessage(message, inputDestination(targetFunction));
if (log.isDebugEnabled()) {
log.debug("Wrapped input msg " + wrappedInputMessage);
}
Tracer tracer = this.tracing.tracer();
Object result;
Throwable throwable = null;
try (Tracer.SpanInScope ws = tracer
.withSpanInScope(wrappedInputMessage.childSpan.start())) {
result = targetFunction.apply(wrappedInputMessage.msg);
}
catch (Exception e) {
throwable = e;
throw e;
}
finally {
traceMessageHandler.afterMessageHandled(wrappedInputMessage.childSpan,
throwable);
}
Message msgResult = toMessage(result);
MessageAndSpan wrappedOutputMessage = traceMessageHandler.wrapOutputMessage(
msgResult,
TraceContextOrSamplingFlags
.create(wrappedInputMessage.parentSpan.context()),
outputDestination(targetFunction));
if (log.isDebugEnabled()) {
log.debug("Wrapped output msg " + wrappedOutputMessage);
}
traceMessageHandler.afterMessageHandled(wrappedOutputMessage.span, null);
return wrappedOutputMessage.msg;
}
private Message toMessage(Object result) {
if (!(result instanceof Message)) {
return MessageBuilder.withPayload(result).build();
}
return (Message) result;
}
private String inputDestination(
SimpleFunctionRegistry.FunctionInvocationWrapper targetFunction) {
String functionDefinition = targetFunction.getFunctionDefinition();
return functionToDestinationCache
.computeIfAbsent(functionDefinition,
s -> this.environment.getProperty(
"spring.cloud.stream.bindings." + s + "-in-0.destination",
s));
}
private String outputDestination(
SimpleFunctionRegistry.FunctionInvocationWrapper targetFunction) {
String functionDefinition = targetFunction.getFunctionDefinition();
return functionToDestinationCache.computeIfAbsent(functionDefinition,
s -> this.environment.getProperty(
"spring.cloud.stream.bindings." + s + "-out-0.destination", s));
}
@Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
if (log.isDebugEnabled()) {
log.debug("Context refreshed, will reset the cache");
}
functionToDestinationCache.clear();
}
}

View File

@@ -0,0 +1,361 @@
/*
* Copyright 2013-2020 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
*
* https://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.instrument.messaging;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import brave.Span;
import brave.SpanCustomizer;
import brave.Tracer;
import brave.Tracing;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.internal.SpanNameUtil;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.StringUtils;
// TODO: Duplicates a lot from TraceChannelInterceptor, need to figure out how to merge the two
class TraceMessageHandler {
private static final Log log = LogFactory.getLog(TraceMessageHandler.class);
/**
* Using the literal "broker" until we come up with a better solution.
*
* <p>
* If the message originated from a binder (consumer binding), there will be different
* headers present (e.g. "KafkaHeaders.RECEIVED_TOPIC" Vs.
* "AmqpHeaders.CONSUMER_QUEUE" (unless the application removes them before sending).
* These don't represent the broker, rather a queue, and in any case the heuristics
* are not great. At least we might be able to tell if this is rabbit or not (ex how
* spring-rabbit works). We need to think this through before making an api, possibly
* experimenting.
*
* <p>
* If the app is outbound only (producer), there's no indication of what type the
* destination broker is. This may hint at a non-manual solution being overwriting the
* remoteServiceName later, similar to how servlet instrumentation lazy set
* "http.route".
*/
private static final String REMOTE_SERVICE_NAME = "broker";
private static final String TRACE_HANDLER_PARENT_SPAN = "traceHandlerParentSpan";
private final Tracing tracing;
private final Tracer tracer;
private final TraceContext.Injector<MessageHeaderAccessor> injector;
private final TraceContext.Extractor<MessageHeaderAccessor> extractor;
private final Function<TraceContext, Span> preSendFunction;
private final TriConsumer<MessageHeaderAccessor, Span, Span> preSendMessageManipulator;
private final Function<TraceContext, Span> outputMessageSpanFunction;
TraceMessageHandler(Tracing tracing, Function<TraceContext, Span> preSendFunction,
TriConsumer<MessageHeaderAccessor, Span, Span> preSendMessageManipulator,
Function<TraceContext, Span> outputMessageSpanFunction) {
this.tracing = tracing;
this.tracer = tracing.tracer();
this.injector = tracing.propagation().injector(MessageHeaderPropagation.INSTANCE);
this.extractor = tracing.propagation()
.extractor(MessageHeaderPropagation.INSTANCE);
// TODO: Abstractions to reuse in TraceChannelInterceptors?
this.preSendFunction = preSendFunction;
this.preSendMessageManipulator = preSendMessageManipulator;
this.outputMessageSpanFunction = outputMessageSpanFunction;
}
static TraceMessageHandler forNonSpringIntegration(Tracing tracing) {
Tracer tracer = tracing.tracer();
Function<TraceContext, Span> preSendFunction = ctx -> tracer
.nextSpan(TraceContextOrSamplingFlags.create(ctx)).name("handle").start();
TriConsumer<MessageHeaderAccessor, Span, Span> preSendMessageManipulator = (
headers, parentSpan, childSpan) -> {
headers.setHeader("traceHandlerParentSpan", parentSpan);
headers.setHeader(Span.class.getName(), childSpan);
};
Function<TraceContext, Span> postReceiveFunction = ctx -> tracer
.nextSpan(TraceContextOrSamplingFlags.create(ctx));
return new TraceMessageHandler(tracing, preSendFunction,
preSendMessageManipulator, postReceiveFunction);
}
/**
* Wraps the given input message with tracing headers and returns a corresponding
* span.
* @param message - message to wrap
* @param destinationName - destination from which the message was received
* @return a tuple with the wrapped message and a corresponding span
*/
MessageAndSpans wrapInputMessage(Message<?> message, String destinationName) {
MessageHeaderAccessor headers = mutableHeaderAccessor(message);
TraceContextOrSamplingFlags extracted = this.extractor.extract(headers);
// Start and finish a consumer span as we will immediately process it.
Span consumerSpan = this.tracer.nextSpan(extracted);
if (!consumerSpan.isNoop()) {
consumerSpan.kind(Span.Kind.CONSUMER).start();
consumerSpan.remoteServiceName(REMOTE_SERVICE_NAME);
addTags(consumerSpan, destinationName);
consumerSpan.finish();
}
// create and scope a span for the message processor
Span span = this.preSendFunction.apply(consumerSpan.context());
// remove any trace headers, but don't re-inject as we are synchronously
// processing the
// message and can rely on scoping to access this span later.
clearTracingHeaders(headers);
this.preSendMessageManipulator.accept(headers, consumerSpan, span);
if (log.isDebugEnabled()) {
log.debug(
"Created a handle span after retrieving the message " + consumerSpan);
}
if (message instanceof ErrorMessage) {
return new MessageAndSpans(new ErrorMessage((Throwable) message.getPayload(),
headers.getMessageHeaders()), consumerSpan, span);
}
headers.setImmutable();
return new MessageAndSpans(
new GenericMessage<>(message.getPayload(), headers.getMessageHeaders()),
consumerSpan, span);
}
Span spanFromMessage(Message<?> message) {
MessageHeaderAccessor headers = mutableHeaderAccessor(message);
Span span = span(headers, Span.class.getName());
if (span != null) {
return span;
}
span = span(headers, TRACE_HANDLER_PARENT_SPAN);
if (span != null) {
return span;
}
TraceContextOrSamplingFlags extracted = this.extractor.extract(headers);
if (extracted == TraceContextOrSamplingFlags.EMPTY) {
return null;
}
return this.tracer.nextSpan(extracted);
}
private void addTags(SpanCustomizer result, String destinationName) {
if (StringUtils.hasText(destinationName)) {
result.tag("channel", SpanNameUtil.shorten(destinationName));
}
}
/**
* Called either when message got received and processed or message got sent.
* @param span - span that corresponds to the given operation
* @param ex - an optional exception that occurred while processing / sending.
*/
void afterMessageHandled(Span span, Throwable ex) {
if (log.isDebugEnabled()) {
log.debug("Will finish the current span after message handled " + span);
}
finishSpan(span, ex);
}
Span parentSpan(Message message) {
return span(mutableHeaderAccessor(message), "traceHandlerParentSpan");
}
Span consumerSpan(Message message) {
return span(mutableHeaderAccessor(message), Span.class.getName());
}
private Span span(MessageHeaderAccessor headerAccessor, String key) {
return headerAccessor.getMessageHeaders().get(key, Span.class);
}
/**
* Wraps the given output message with tracing headers and returns a corresponding
* span.
* @param message - message to wrap
* @param destinationName - destination to which the message should be sent
* @return a tuple with the wrapped message and a corresponding span
*/
MessageAndSpan wrapOutputMessage(Message<?> message,
TraceContextOrSamplingFlags parentSpan, String destinationName) {
Message<?> retrievedMessage = getMessage(message);
MessageHeaderAccessor headers = mutableHeaderAccessor(retrievedMessage);
Span span = this.outputMessageSpanFunction.apply(parentSpan.context());
clearTracingHeaders(headers);
this.injector.inject(span.context(), headers);
markProducerSpan(headers, span, destinationName);
if (log.isDebugEnabled()) {
log.debug("Created a new span output message " + span);
}
return new MessageAndSpan(outputMessage(message, retrievedMessage, headers),
span);
}
private void markProducerSpan(MessageHeaderAccessor headers, Span span,
String destinationName) {
if (!span.isNoop()) {
span.kind(Span.Kind.PRODUCER).name("send").start();
span.remoteServiceName(toRemoteServiceName(headers));
addTags(span, destinationName);
}
}
private String toRemoteServiceName(MessageHeaderAccessor headers) {
for (String key : headers.getMessageHeaders().keySet()) {
if (key.startsWith("kafka_")) {
return "kafka";
}
else if (key.startsWith("amqp_")) {
return "rabbitmq";
}
}
return REMOTE_SERVICE_NAME;
}
private Message<?> outputMessage(Message<?> originalMessage,
Message<?> retrievedMessage, MessageHeaderAccessor additionalHeaders) {
MessageHeaderAccessor headers = MessageHeaderAccessor
.getMutableAccessor(originalMessage);
clearTechnicalTracingHeaders(headers);
if (originalMessage instanceof ErrorMessage) {
ErrorMessage errorMessage = (ErrorMessage) originalMessage;
headers.copyHeaders(MessageHeaderPropagation.propagationHeaders(
additionalHeaders.getMessageHeaders(),
this.tracing.propagation().keys()));
return new ErrorMessage(errorMessage.getPayload(),
isWebSockets(headers) ? headers.getMessageHeaders()
: new MessageHeaders(headers.getMessageHeaders()),
errorMessage.getOriginalMessage());
}
headers.copyHeaders(additionalHeaders.getMessageHeaders());
return new GenericMessage<>(retrievedMessage.getPayload(),
isWebSockets(headers) ? headers.getMessageHeaders()
: new MessageHeaders(headers.getMessageHeaders()));
}
private boolean isWebSockets(MessageHeaderAccessor headerAccessor) {
return headerAccessor.getMessageHeaders().containsKey("stompCommand")
|| headerAccessor.getMessageHeaders().containsKey("simpMessageType");
}
private Message<?> getMessage(Message<?> message) {
Object payload = message.getPayload();
if (payload instanceof MessagingException) {
MessagingException e = (MessagingException) payload;
Message<?> failedMessage = e.getFailedMessage();
return failedMessage != null ? failedMessage : message;
}
return message;
}
private MessageHeaderAccessor mutableHeaderAccessor(Message<?> message) {
MessageHeaderAccessor headers = MessageHeaderAccessor.getMutableAccessor(message);
headers.setLeaveMutable(true);
return headers;
}
private void clearTracingHeaders(MessageHeaderAccessor headers) {
List<String> keysToRemove = new ArrayList<>(this.tracing.propagation().keys());
keysToRemove.add(Span.class.getName());
keysToRemove.add("traceHandlerParentSpan");
MessageHeaderPropagation.removeAnyTraceHeaders(headers, keysToRemove);
}
private void clearTechnicalTracingHeaders(MessageHeaderAccessor headers) {
MessageHeaderPropagation.removeAnyTraceHeaders(headers,
Arrays.asList(Span.class.getName(), "traceHandlerParentSpan"));
}
private void finishSpan(Span span, Throwable error) {
if (span == null || span.isNoop()) {
return;
}
if (error != null) { // an error occurred, adding error to span
String message = error.getMessage();
if (message == null) {
message = error.getClass().getSimpleName();
}
span.tag("error", message);
}
span.finish();
}
}
class MessageAndSpan {
final Message msg;
final Span span;
MessageAndSpan(Message msg, Span span) {
this.msg = msg;
this.span = span;
}
@Override
public String toString() {
return "MessageAndSpan{" + "msg=" + this.msg + ", span=" + this.span + '}';
}
}
class MessageAndSpans {
final Message msg;
final Span parentSpan;
final Span childSpan;
MessageAndSpans(Message msg, Span parentSpan, Span childSpan) {
this.msg = msg;
this.parentSpan = parentSpan;
this.childSpan = childSpan;
}
@Override
public String toString() {
return "MessageAndSpans{" + "msg=" + msg + ", parentSpan=" + parentSpan
+ ", childSpan=" + childSpan + '}';
}
}
interface TriConsumer<K, V, S> {
/**
* Performs the operation given the specified arguments.
* @param k the first input argument
* @param v the second input argument
* @param s the third input argument
*/
void accept(K k, V v, S s);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -16,6 +16,9 @@
package org.springframework.cloud.sleuth.instrument.messaging;
import java.util.Arrays;
import java.util.List;
/**
* Contains trace related messaging headers. The deprecated headers contained `-` which
* for example in the JMS specs is invalid. That's why the public constants in this class
@@ -58,6 +61,9 @@ public final class TraceMessageHeaders {
*/
public static final String SPAN_FLAGS_NAME = "spanFlags";
static List<String> HEADERS = Arrays.asList(SAMPLED_NAME, SPAN_FLAGS_NAME,
SPAN_ID_NAME, SPAN_NAME_NAME, TRACE_ID_NAME, PARENT_ID_NAME);
private TraceMessageHeaders() {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -20,12 +20,18 @@ import brave.Tracing;
import brave.propagation.Propagation;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.function.context.FunctionCatalog;
import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration;
import org.springframework.cloud.stream.config.BinderFactoryAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.interceptor.GlobalChannelInterceptorWrapper;
import org.springframework.integration.config.GlobalChannelInterceptor;
@@ -46,8 +52,8 @@ import org.springframework.messaging.support.MessageHeaderAccessor;
@AutoConfigureAfter({ TraceAutoConfiguration.class,
TraceSpringMessagingAutoConfiguration.class })
@OnMessagingEnabled
@ConditionalOnProperty(value = "spring.sleuth.integration.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SleuthMessagingProperties.class)
@Conditional(TracingChannelInterceptorCondition.class)
class TraceSpringIntegrationAutoConfiguration {
@Bean
@@ -68,3 +74,34 @@ class TraceSpringIntegrationAutoConfiguration {
}
}
final class TracingChannelInterceptorCondition extends AnyNestedCondition {
private TracingChannelInterceptorCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnMissingClass("org.springframework.cloud.function.context.FunctionCatalog")
@ConditionalOnProperty(value = "spring.sleuth.integration.enabled",
matchIfMissing = true)
static class OnFunctionMissing {
}
@ConditionalOnClass(FunctionCatalog.class)
@ConditionalOnBean(BinderFactoryAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.sleuth.integration.enabled",
matchIfMissing = true)
static class OnFunctionPresentAndEnableBinding {
}
@ConditionalOnClass(FunctionCatalog.class)
@ConditionalOnMissingBean(BinderFactoryAutoConfiguration.class)
@ConditionalOnProperty(value = "spring.sleuth.integration.enabled",
havingValue = "true")
static class OnFunctionPresentEnableBindingOffAndIntegrationExplicitlyOn {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -24,6 +24,8 @@ import brave.propagation.TraceContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Fuseable;
import reactor.core.Scannable;
@@ -96,13 +98,6 @@ public abstract class ReactorSleuth {
return sub;
}
Context context = sub.currentContext();
if (log.isTraceEnabled()) {
log.trace("Spring context [" + springContext + "], Reactor context ["
+ context + "], name [" + name(sub) + "]");
}
// Try to get the current trace context bean, lenient when there are problems
CurrentTraceContext currentTraceContext = lazyCurrentTraceContext.get();
if (currentTraceContext == null) {
@@ -118,6 +113,12 @@ public abstract class ReactorSleuth {
return sub;
}
Context context = contextWithBeans(springContext, sub);
if (log.isTraceEnabled()) {
log.trace("Spring context [" + springContext + "], Reactor context ["
+ context + "], name [" + name(sub) + "]");
}
TraceContext parent = traceContext(context, currentTraceContext);
if (parent == null) {
return sub; // no need to scope a null parent
@@ -127,11 +128,46 @@ public abstract class ReactorSleuth {
log.trace("Creating a scope passing span subscriber with Reactor Context "
+ "[" + context + "] and name [" + name(sub) + "]");
}
// if (runStyle == Scannable.Attr.RunStyle.SYNC) {
// return sub;
// }
return new ScopePassingSpanSubscriber<>(sub, context, currentTraceContext,
parent);
});
}
private static <T> Context contextWithBeans(
ConfigurableApplicationContext springContext, CoreSubscriber<? super T> sub) {
Context context = sub.currentContext();
if (!context.hasKey(Tracing.class)) {
context = context.put(Tracing.class, springContext.getBean(Tracing.class));
}
if (!context.hasKey(CurrentTraceContext.class)) {
context = context.put(CurrentTraceContext.class,
springContext.getBean(CurrentTraceContext.class));
}
return context;
}
static <T> Function<? super Publisher<T>, ? extends Publisher<T>> springContextSpanOperator(
ConfigurableApplicationContext springContext) {
if (log.isTraceEnabled()) {
log.trace("Spring Context passing operator [" + springContext + "]");
}
return Operators.liftPublisher((p, sub) -> {
// We don't scope scalar results as they happen in an instant. This prevents
// excessive overhead when using Flux/Mono #just, #empty, #error, etc.
if (p instanceof Fuseable.ScalarCallable) {
return sub;
}
if (!springContext.isActive()) {
return sub;
}
final Context context = contextWithBeans(springContext, sub);
return new SleuthContextOperator<>(context, sub);
});
}
static String name(CoreSubscriber<?> sub) {
return Scannable.from(sub).name();
}
@@ -147,3 +183,54 @@ public abstract class ReactorSleuth {
}
}
class SleuthContextOperator<T> implements Subscription, CoreSubscriber<T> {
private final Context context;
private final Subscriber<? super T> subscriber;
private Subscription s;
SleuthContextOperator(Context context, Subscriber<? super T> subscriber) {
this.context = context;
this.subscriber = subscriber;
}
@Override
public void onSubscribe(Subscription subscription) {
this.s = subscription;
this.subscriber.onSubscribe(this);
}
@Override
public void request(long n) {
this.s.request(n);
}
@Override
public void cancel() {
this.s.cancel();
}
@Override
public void onNext(T o) {
this.subscriber.onNext(o);
}
@Override
public void onError(Throwable throwable) {
this.subscriber.onError(throwable);
}
@Override
public void onComplete() {
this.subscriber.onComplete();
}
@Override
public Context currentContext() {
return this.context;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -37,9 +37,14 @@ class SleuthReactorProperties {
* always contain the tracing entries in each operator. When false decorates on last
* operator, will be more performing, but logging might not always contain the tracing
* entries.
* @deprecated use explicit value via
* {@link SleuthReactorProperties#instrumentationType}
*/
@Deprecated
private boolean decorateOnEach = true;
private InstrumentationType instrumentationType = InstrumentationType.DECORATE_ON_EACH;
public boolean isEnabled() {
return this.enabled;
}
@@ -48,12 +53,42 @@ class SleuthReactorProperties {
this.enabled = enabled;
}
@Deprecated
public boolean isDecorateOnEach() {
return this.decorateOnEach;
}
@Deprecated
public void setDecorateOnEach(boolean decorateOnEach) {
this.decorateOnEach = decorateOnEach;
this.instrumentationType = decorateOnEach ? InstrumentationType.DECORATE_ON_EACH
: InstrumentationType.DECORATE_ON_LAST;
}
public InstrumentationType getInstrumentationType() {
return this.instrumentationType;
}
public void setInstrumentationType(InstrumentationType instrumentationType) {
this.instrumentationType = instrumentationType;
}
public enum InstrumentationType {
/**
* Wraps each operator in a Sleuth representation.
*/
DECORATE_ON_EACH,
/**
* Wraps only the last operator in Sleuth representation.
*/
DECORATE_ON_LAST,
/**
* Does not automatically wrap any operators.
*/
MANUAL;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.
@@ -16,11 +16,14 @@
package org.springframework.cloud.sleuth.instrument.reactor;
import javax.annotation.PreDestroy;
import java.io.Closeable;
import java.io.IOException;
import java.util.function.Function;
import brave.Tracing;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@@ -45,6 +48,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import static org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth.scopePassingSpanOperator;
import static org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth.springContextSpanOperator;
import static org.springframework.cloud.sleuth.instrument.reactor.TraceReactorAutoConfiguration.TraceReactorConfiguration.SLEUTH_TRACE_REACTOR_KEY;
/**
@@ -77,29 +81,6 @@ class TraceReactorAutoConfiguration {
@Autowired
ConfigurableApplicationContext springContext;
@PreDestroy
public void cleanupHooks() {
if (log.isTraceEnabled()) {
log.trace("Cleaning up hooks");
}
SleuthReactorProperties reactorProperties = this.springContext
.getBean(SleuthReactorProperties.class);
if (reactorProperties.isDecorateOnEach()) {
if (log.isTraceEnabled()) {
log.trace("Resetting onEach operator instrumentation");
}
Hooks.resetOnEachOperator(SLEUTH_TRACE_REACTOR_KEY);
}
else {
if (log.isTraceEnabled()) {
log.trace("Resetting onLast operator instrumentation");
}
Hooks.resetOnLastOperator(SLEUTH_TRACE_REACTOR_KEY);
}
Schedulers
.removeExecutorServiceDecorator(SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY);
}
@Bean
@ConditionalOnMissingBean
HookRegisteringBeanDefinitionRegistryPostProcessor traceHookRegisteringBeanDefinitionRegistryPostProcessor(
@@ -149,26 +130,32 @@ class HooksRefresher implements ApplicationListener<RefreshScopeRefreshedEvent>
}
Hooks.resetOnEachOperator(SLEUTH_TRACE_REACTOR_KEY);
Hooks.resetOnLastOperator(SLEUTH_TRACE_REACTOR_KEY);
if (this.reactorProperties.isDecorateOnEach()) {
switch (this.reactorProperties.getInstrumentationType()) {
case DECORATE_ON_EACH:
if (log.isTraceEnabled()) {
log.trace("Decorating onEach operator instrumentation");
}
Hooks.onEachOperator(SLEUTH_TRACE_REACTOR_KEY,
scopePassingSpanOperator(this.context));
}
else {
break;
case DECORATE_ON_LAST:
if (log.isTraceEnabled()) {
log.trace("Decorating onLast operator instrumentation");
}
Hooks.onLastOperator(SLEUTH_TRACE_REACTOR_KEY,
scopePassingSpanOperator(this.context));
break;
case MANUAL:
Hooks.onLastOperator(SLEUTH_TRACE_REACTOR_KEY,
springContextSpanOperator(this.context));
break;
}
}
}
class HookRegisteringBeanDefinitionRegistryPostProcessor
implements BeanDefinitionRegistryPostProcessor {
implements BeanDefinitionRegistryPostProcessor, Closeable {
private static final Log log = LogFactory
.getLog(HookRegisteringBeanDefinitionRegistryPostProcessor.class);
@@ -191,21 +178,25 @@ class HookRegisteringBeanDefinitionRegistryPostProcessor
static void setupHooks(ConfigurableApplicationContext springContext) {
ConfigurableEnvironment environment = springContext.getEnvironment();
boolean decorateOnEach = environment.getProperty(
SleuthReactorProperties.InstrumentationType property = environment.getProperty(
"spring.sleuth.reactor.instrumentation-type",
SleuthReactorProperties.InstrumentationType.class,
SleuthReactorProperties.InstrumentationType.DECORATE_ON_EACH);
Boolean decorateOnEach = environment.getProperty(
"spring.sleuth.reactor.decorate-on-each", Boolean.class, true);
if (decorateOnEach) {
if (log.isTraceEnabled()) {
log.trace("Decorating onEach operator instrumentation");
}
Hooks.onEachOperator(SLEUTH_TRACE_REACTOR_KEY,
scopePassingSpanOperator(springContext));
if (!decorateOnEach) {
log.warn(
"You're using the deprecated [spring.sleuth.reactor.decorate-on-each] property. Please use the [spring.sleuth.reactor.instrumentation-type] one instead.");
decorateOnLast(scopePassingSpanOperator(springContext));
}
else {
if (log.isTraceEnabled()) {
log.trace("Decorating onLast operator instrumentation");
}
Hooks.onLastOperator(SLEUTH_TRACE_REACTOR_KEY,
scopePassingSpanOperator(springContext));
else if (property == SleuthReactorProperties.InstrumentationType.DECORATE_ON_EACH) {
decorateOnEach(springContext);
}
else if (property == SleuthReactorProperties.InstrumentationType.DECORATE_ON_LAST) {
decorateOnLast(scopePassingSpanOperator(springContext));
}
else if (property == SleuthReactorProperties.InstrumentationType.MANUAL) {
decorateOnLast(springContextSpanOperator(springContext));
}
Schedulers.setExecutorServiceDecorator(
TraceReactorAutoConfiguration.SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY,
@@ -214,4 +205,31 @@ class HookRegisteringBeanDefinitionRegistryPostProcessor
springContext, scheduledExecutorService));
}
private static void decorateOnLast(
Function<? super Publisher<Object>, ? extends Publisher<Object>> function) {
if (log.isTraceEnabled()) {
log.trace("Decorating onLast operator instrumentation");
}
Hooks.onLastOperator(SLEUTH_TRACE_REACTOR_KEY, function);
}
private static void decorateOnEach(ConfigurableApplicationContext springContext) {
if (log.isTraceEnabled()) {
log.trace("Decorating onEach operator instrumentation");
}
Hooks.onEachOperator(SLEUTH_TRACE_REACTOR_KEY,
scopePassingSpanOperator(springContext));
}
@Override
public void close() throws IOException {
if (log.isTraceEnabled()) {
log.trace("Cleaning up hooks");
}
Hooks.resetOnEachOperator(SLEUTH_TRACE_REACTOR_KEY);
Hooks.resetOnLastOperator(SLEUTH_TRACE_REACTOR_KEY);
Schedulers.removeExecutorServiceDecorator(
TraceReactorAutoConfiguration.SLEUTH_REACTOR_EXECUTOR_SERVICE_KEY);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2019 the original author or authors.
* Copyright 2013-2020 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.

Some files were not shown because too many files have changed in this diff Show More