/** * Copyright 2015-2016 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package zipkin.benchmarks; import com.google.common.io.ByteStreams; import com.twitter.zipkin.thriftjava.Annotation; import com.twitter.zipkin.thriftjava.BinaryAnnotation; import java.io.IOException; import java.util.concurrent.TimeUnit; import org.apache.thrift.TDeserializer; import org.apache.thrift.TException; import org.apache.thrift.TSerializer; import org.apache.thrift.protocol.TBinaryProtocol; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; 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 zipkin.Codec; import zipkin.Endpoint; import zipkin.Span; /** * This compares the speed of the bundled java codec with the approach used in the scala * implementation. Re-run this benchmark when changing internals of {@link zipkin.Codec}. * * <p>The {@link zipkin.Codec bundled java codec} aims to be both small in size (i.e. does not * significantly increase the size of zipkin's jar), and efficient. It may not always be fastest, * but we should try to keep it competitive. */ @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @State(Scope.Thread) @Threads(1) public class CodecBenchmarks { static final TBinaryProtocol.Factory TBINARY_PROTOCOL_FACTORY = new TBinaryProtocol.Factory(); static final byte[] localSpanJson = read("/span-local.json"); static final Span localSpan = Codec.JSON.readSpan(localSpanJson); static final byte[] localSpanThrift = Codec.THRIFT.writeSpan(localSpan); static final com.twitter.zipkin.thriftjava.Span localSpanLibThrift = deserialize(localSpanThrift); @Benchmark public Span readLocalSpan_json_zipkin() { return Codec.JSON.readSpan(localSpanJson); } @Benchmark public Span readLocalSpan_thrift_zipkin() { return Codec.THRIFT.readSpan(localSpanThrift); } @Benchmark public Span readLocalSpan_thrift_libthrift() { return toZipkinSpan(deserialize(localSpanThrift)); } @Benchmark public byte[] writeLocalSpan_json_zipkin() { return Codec.JSON.writeSpan(localSpan); } @Benchmark public byte[] writeLocalSpan_thrift_zipkin() { return Codec.THRIFT.writeSpan(localSpan); } @Benchmark public byte[] writeLocalSpan_thrift_libthrift() throws TException { return serialize(localSpanLibThrift); } static final byte[] clientSpanJson = read("/span-client.json"); static final Span clientSpan = Codec.JSON.readSpan(clientSpanJson); static final byte[] clientSpanThrift = Codec.THRIFT.writeSpan(clientSpan); static final com.twitter.zipkin.thriftjava.Span clientSpanLibThrift = deserialize(clientSpanThrift); @Benchmark public Span readClientSpan_json_zipkin() { return Codec.JSON.readSpan(clientSpanJson); } @Benchmark public Span readClientSpan_thrift_zipkin() { return Codec.THRIFT.readSpan(clientSpanThrift); } @Benchmark public Span readClientSpan_thrift_libthrift() { return toZipkinSpan(deserialize(clientSpanThrift)); } @Benchmark public byte[] writeClientSpan_json_zipkin() { return Codec.JSON.writeSpan(clientSpan); } @Benchmark public byte[] writeClientSpan_thrift_zipkin() { return Codec.THRIFT.writeSpan(clientSpan); } @Benchmark public byte[] writeClientSpan_thrift_libthrift() throws TException { return serialize(clientSpanLibThrift); } static final byte[] rpcSpanJson = read("/span-rpc.json"); static final Span rpcSpan = Codec.JSON.readSpan(rpcSpanJson); static final byte[] rpcSpanThrift = Codec.THRIFT.writeSpan(rpcSpan); static final com.twitter.zipkin.thriftjava.Span rpcSpanLibThrift = deserialize(rpcSpanThrift); @Benchmark public Span readRpcSpan_json_zipkin() { return Codec.JSON.readSpan(rpcSpanJson); } @Benchmark public Span readRpcSpan_thrift_zipkin() { return Codec.THRIFT.readSpan(rpcSpanThrift); } @Benchmark public Span readRpcSpan_thrift_libthrift() { return toZipkinSpan(deserialize(rpcSpanThrift)); } @Benchmark public byte[] writeRpcSpan_json_zipkin() { return Codec.JSON.writeSpan(rpcSpan); } @Benchmark public byte[] writeRpcSpan_thrift_zipkin() { return Codec.THRIFT.writeSpan(rpcSpan); } @Benchmark public byte[] writeRpcSpan_thrift_libthrift() throws TException { return serialize(rpcSpanLibThrift); } static final byte[] rpcV6SpanJson = read("/span-rpc-ipv6.json"); static final Span rpcV6Span = Codec.JSON.readSpan(rpcV6SpanJson); static final byte[] rpcV6SpanThrift = Codec.THRIFT.writeSpan(rpcV6Span); static final com.twitter.zipkin.thriftjava.Span rpcV6SpanLibThrift = deserialize(rpcV6SpanThrift); @Benchmark public Span readRpcV6Span_json_zipkin() { return Codec.JSON.readSpan(rpcV6SpanJson); } @Benchmark public Span readRpcV6Span_thrift_zipkin() { return Codec.THRIFT.readSpan(rpcV6SpanThrift); } @Benchmark public Span readRpcV6Span_thrift_libthrift() { return toZipkinSpan(deserialize(rpcV6SpanThrift)); } @Benchmark public byte[] writeRpcV6Span_json_zipkin() { return Codec.JSON.writeSpan(rpcV6Span); } @Benchmark public byte[] writeRpcV6Span_thrift_zipkin() { return Codec.THRIFT.writeSpan(rpcV6Span); } @Benchmark public byte[] writeRpcV6Span_thrift_libthrift() throws TException { return serialize(rpcV6SpanLibThrift); } // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + CodecBenchmarks.class.getSimpleName() + ".*") .build(); new Runner(opt).run(); } static byte[] read(String resource) { try { return ByteStreams.toByteArray(CodecBenchmarks.class.getResourceAsStream(resource)); } catch (IOException e) { throw new IllegalStateException(e); } } private byte[] serialize(com.twitter.zipkin.thriftjava.Span thriftSpan) throws TException { // TSerializer isn't thread-safe return new TSerializer(TBINARY_PROTOCOL_FACTORY).serialize(thriftSpan); } private static com.twitter.zipkin.thriftjava.Span deserialize(byte[] serialized) { com.twitter.zipkin.thriftjava.Span result = new com.twitter.zipkin.thriftjava.Span(); try { // TDeserializer isn't thread-safe new TDeserializer(TBINARY_PROTOCOL_FACTORY).deserialize(result, serialized); } catch (TException e) { throw new AssertionError(e); } return result; } /** * {@link zipkin.Span.Builder}, validates, doesn't return null for fields that aren't nullable, * uses immutable collections, etc. When comparing codec, make sure you copy-out as structs like * libthrift do no validation, which is cheaper, but not usable in zipkin. */ private static Span toZipkinSpan(com.twitter.zipkin.thriftjava.Span libthriftSpan) { Span.Builder builder = Span.builder() .traceId(libthriftSpan.trace_id) .id(libthriftSpan.id) .parentId(libthriftSpan.isSetParent_id() ? libthriftSpan.parent_id : null) .name(libthriftSpan.name) .timestamp(libthriftSpan.isSetTimestamp() ? libthriftSpan.timestamp : null) .duration(libthriftSpan.isSetDuration() ? libthriftSpan.duration : null) .debug(libthriftSpan.isSetDebug() ? libthriftSpan.debug : null); if (libthriftSpan.isSetAnnotations()) { for (Annotation a : libthriftSpan.annotations) { builder.addAnnotation(zipkin.Annotation.create(a.timestamp, a.value, a.isSetHost() ? Endpoint.builder() .serviceName(a.host.service_name) .ipv4(a.host.ipv4) .ipv6(a.host.getIpv6()).build() : null )); } } if (libthriftSpan.isSetBinary_annotations()) { for (BinaryAnnotation b : libthriftSpan.binary_annotations) { builder.addBinaryAnnotation(zipkin.BinaryAnnotation.create(b.key, b.getValue(), zipkin.BinaryAnnotation.Type.fromValue(b.getAnnotation_type().getValue()), b.isSetHost() ? Endpoint.builder() .serviceName(b.host.service_name) .ipv4(b.host.ipv4) .ipv6(b.host.getIpv6()).build() : null )); } } return builder.build(); } }