Skip to content

Commit

Permalink
Adds EndToEndBenchmarks (#500)
Browse files Browse the repository at this point in the history
This adds a benchmark of our canonical zipkin example. The goal is to
show relative overhead, conceding some of this will be skewed a bit.

EndToEndBenchmarks takes the same approach as example projects like
https://github.com/openzipkin/sleuth-webmvc-example, using the fastest
components we have: currently servlet and okhttp instrumentation.

http://localhost:PORT/BASEPATH calls http://localhost:PORT/BASEPATH/api
This simulates a simple ingress -> egress scenario

EndToEndBenchmarks include the following:

* server_get - uninstrumented baseline
* tracedServer_get - server starts and propagates a new trace
* tracedServer_get_resumeTrace - server resumes an existing trace
* unsampledServer_get - server doesn't sample, but propagates

Here's a sample run:
```
Benchmark                                        Mode  Cnt    Score   Error  Units
EndToEndBenchmarks.server_get                    avgt   15  141.483 ± 9.793  us/op
EndToEndBenchmarks.tracedServer_get              avgt   15  160.231 ± 3.479  us/op
EndToEndBenchmarks.tracedServer_get_resumeTrace  avgt   15  165.453 ± 7.216  us/op
EndToEndBenchmarks.unsampledServer_get           avgt   15  153.022 ± 4.111  us/op
```

In this case, it appears resuming a trace takes longer than starting a
new one. While this can be explained, having benchmarks allow us to
drive numbers down relatively objectively.
  • Loading branch information
adriancole committed Sep 19, 2017
1 parent 2014931 commit 7c6e56c
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 30 deletions.
39 changes: 20 additions & 19 deletions brave/src/main/java/brave/Tracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public Tracer build() {
* instead.
*/
public Span newTrace() {
return ensureSampled(nextContext(null, SamplingFlags.EMPTY));
return toSpan(nextContext(null, SamplingFlags.EMPTY));
}

/**
Expand All @@ -169,7 +169,14 @@ public Span newTrace() {
public final Span joinSpan(TraceContext context) {
if (context == null) throw new NullPointerException("context == null");
// If we are joining a trace, we are sharing IDs with the caller
return ensureSampled(context.toBuilder().shared(true).build());
// If the sampled flag was left unset, we need to make the decision here
TraceContext.Builder builder = context.toBuilder();
if (context.sampled() == null) {
builder.sampled(sampler.isSampled(context.traceId()));
} else {
builder.shared(true);
}
return toSpan(builder.build());
}

/**
Expand All @@ -190,7 +197,7 @@ public final Span joinSpan(TraceContext context) {
* }</pre>
*/
public Span newTrace(SamplingFlags samplingFlags) {
return ensureSampled(nextContext(null, samplingFlags));
return toSpan(nextContext(null, samplingFlags));
}

/** Converts the context as-is to a Span object */
Expand All @@ -211,30 +218,24 @@ public Span newChild(TraceContext parent) {
if (Boolean.FALSE.equals(parent.sampled())) {
return NoopSpan.create(parent);
}
return ensureSampled(nextContext(parent, parent));
}

Span ensureSampled(TraceContext context) {
// If the sampled flag was left unset, we need to make the decision here
if (context.sampled() == null) {
context = context.toBuilder()
.sampled(sampler.isSampled(context.traceId()))
.shared(false)
.build();
}
return toSpan(context);
return toSpan(nextContext(parent, parent));
}

TraceContext nextContext(@Nullable TraceContext parent, SamplingFlags samplingFlags) {
long nextId = Platform.get().randomLong();
if (parent != null) {
return parent.toBuilder().spanId(nextId).parentId(parent.spanId()).shared(false).build();
return parent.toBuilder()
.spanId(nextId)
.parentId(parent.spanId())
.shared(false)
.build();
}
Boolean sampled = samplingFlags.sampled();
if (sampled == null) sampled = sampler.isSampled(nextId);
return TraceContext.newBuilder()
.sampled(samplingFlags.sampled())
.sampled(sampled)
.debug(samplingFlags.debug())
.traceIdHigh(traceId128Bit ? Platform.get().randomLong() : 0L)
.traceId(nextId)
.traceIdHigh(traceId128Bit ? Platform.get().randomLong() : 0L).traceId(nextId)
.spanId(nextId).build();
}

Expand Down
4 changes: 2 additions & 2 deletions brave/src/main/java/brave/propagation/B3Propagation.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ static final class B3Extractor<C, K> implements TraceContext.Extractor<C> {
boolean debug = "1".equals(getter.get(carrier, propagation.debugKey));

String traceIdString = getter.get(carrier, propagation.traceIdKey);
if (traceIdString == null) { // return early if there's no trace ID
String spanIdString = getter.get(carrier, propagation.spanIdKey);
if (traceIdString == null || spanIdString == null) { // return early if there's no trace ID
return TraceContextOrSamplingFlags.create(
new SamplingFlags.Builder().sampled(sampled).debug(debug).build()
);
Expand All @@ -124,7 +125,6 @@ static final class B3Extractor<C, K> implements TraceContext.Extractor<C> {
traceIdString.length() == 32 ? lowerHexToUnsignedLong(traceIdString, 0) : 0
);
result.traceId(lowerHexToUnsignedLong(traceIdString));
String spanIdString = getter.get(carrier, propagation.spanIdKey);
if (spanIdString != null) {
result.spanId(lowerHexToUnsignedLong(spanIdString));
}
Expand Down
2 changes: 1 addition & 1 deletion brave/src/main/java/brave/propagation/TraceContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public static abstract class Builder {
public abstract Builder spanId(long spanId);

/** @see TraceContext#sampled */
public abstract Builder sampled(Boolean nullableSampled);
public abstract Builder sampled(@Nullable Boolean nullableSampled);

/** @see TraceContext#debug() */
public abstract Builder debug(boolean debug);
Expand Down
120 changes: 120 additions & 0 deletions instrumentation/benchmarks/src/main/java/brave/EndToEndBenchmarks.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package brave;

import brave.http.HttpServerBenchmarks;
import brave.okhttp3.TracingCallFactory;
import brave.sampler.Sampler;
import brave.servlet.TracingFilter;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
import java.io.IOException;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
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;

import static javax.servlet.DispatcherType.REQUEST;

/** Uses the canonical zipkin frontend-backend app, with the fastest components */
public class EndToEndBenchmarks extends HttpServerBenchmarks {
static volatile int PORT;

static class HelloServlet extends HttpServlet {
final Call.Factory callFactory = new OkHttpClient();

@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.addHeader("Content-Type", "text/plain; charset=UTF-8");
// regardless of how we got here, if we are "api" we just return a string
if (req.getRequestURI().endsWith("/api")) {
resp.getWriter().println(new Date().toString());
} else {
Request request = new Request.Builder().url(new HttpUrl.Builder()
.scheme("http")
.host("127.0.0.1")
.port(PORT)
.encodedPath(req.getRequestURI() + "/api").build()).build();

// If we are tracing, we'll have a scoped call factory available
Call.Factory localCallFactory =
(Call.Factory) req.getAttribute(Call.Factory.class.getName());
if (localCallFactory == null) localCallFactory = callFactory;

resp.getWriter().println(localCallFactory.newCall(request).execute().body().string());
}
}
}

public static class Unsampled extends ForwardingTracingFilter {
public Unsampled() {
super(Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).spanReporter(s -> {
}).build());
}
}

public static class Traced extends ForwardingTracingFilter {
public Traced() {
super(Tracing.newBuilder().spanReporter(s -> {
}).build());
}
}

@Override protected void init(DeploymentInfo servletBuilder) {
servletBuilder.addFilter(new FilterInfo("Unsampled", Unsampled.class))
.addFilterUrlMapping("Unsampled", "/unsampled", REQUEST)
.addFilterUrlMapping("Unsampled", "/unsampled/api", REQUEST)
.addFilter(new FilterInfo("Traced", Traced.class))
.addFilterUrlMapping("Traced", "/traced", REQUEST)
.addFilterUrlMapping("Traced", "/traced/api", REQUEST)
.addServlets(Servlets.servlet("HelloServlet", HelloServlet.class).addMapping("/*"));
}

@Override protected int initServer() throws ServletException {
return PORT = super.initServer();
}

// Convenience main entry-point
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(".*" + EndToEndBenchmarks.class.getSimpleName() + ".*")
.build();

new Runner(opt).run();
}

static class ForwardingTracingFilter implements Filter {
final Filter delegate;
final Call.Factory callFactory;

public ForwardingTracingFilter(Tracing tracing) {
this.delegate = TracingFilter.create(tracing);
this.callFactory = TracingCallFactory.create(tracing, new OkHttpClient());
}

@Override public void init(FilterConfig filterConfig) throws ServletException {
}

@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
servletRequest.setAttribute(Call.Factory.class.getName(), callFactory);
delegate.doFilter(servletRequest, servletResponse, filterChain);
}

@Override public void destroy() {
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package brave.http;

import brave.Tracing;
import brave.propagation.CurrentTraceContext.Scope;
import brave.propagation.TraceContext;
import brave.sampler.Sampler;
import io.undertow.Undertow;
import java.net.InetSocketAddress;
Expand All @@ -12,7 +14,6 @@
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
Expand All @@ -27,7 +28,7 @@
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Threads(2)
@State(Scope.Benchmark)
@State(org.openjdk.jmh.annotations.Scope.Benchmark)
public abstract class HttpClientBenchmarks<C> {
protected abstract C newClient(HttpTracing httpTracing) throws Exception;

Expand All @@ -42,6 +43,8 @@ public abstract class HttpClientBenchmarks<C> {
C client;
C tracedClient;
C unsampledClient;
TraceContext context =
TraceContext.newBuilder().traceIdHigh(333L).traceId(444L).spanId(3).sampled(true).build();

protected String baseUrl() {
return baseUrl;
Expand All @@ -60,10 +63,12 @@ protected String baseUrl() {

client = newClient();
tracedClient = newClient(HttpTracing.create(
Tracing.newBuilder().spanReporter(s -> {}).build()
Tracing.newBuilder().spanReporter(s -> {
}).build()
));
unsampledClient = newClient(HttpTracing.create(
Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).spanReporter(s -> {}).build()
Tracing.newBuilder().sampler(Sampler.NEVER_SAMPLE).spanReporter(s -> {
}).build()
));
}

Expand All @@ -86,4 +91,10 @@ protected String baseUrl() {
@Benchmark public void tracedClient_get() throws Exception {
get(tracedClient);
}

@Benchmark public void tracedClient_get_resumeTrace() throws Exception {
try (Scope scope = Tracing.current().currentTraceContext().newScope(context)) {
get(tracedClient);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected String baseUrl() {
@TearDown(Level.Trial) public void close() throws Exception {
if (server != null) server.stop();
client.dispatcher().executorService().shutdown();
Tracing.current().close();
if (Tracing.current() != null) Tracing.current().close();
}

protected int initServer() throws ServletException {
Expand Down Expand Up @@ -84,9 +84,16 @@ protected int initServer() throws ServletException {
get("/traced");
}

@Benchmark public void tracedServer_get_resumeTrace() throws Exception {
client.newCall(new Request.Builder().url(baseUrl() + "/traced")
.header("X-B3-TraceId", "7180c278b62e8f6a216a2aea45d08fc9")
.header("X-B3-SpanId", "5b4185666d50f68b")
.header("X-B3-Sampled", "1")
.build())
.execute().body().close();
}

void get(String path) throws IOException {
client.newCall(new Request.Builder().url(baseUrl() + path).build())
.execute()
.body().close();
client.newCall(new Request.Builder().url(baseUrl() + path).build()).execute().body().close();
}
}

0 comments on commit 7c6e56c

Please sign in to comment.