diff --git a/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java b/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java index 8bb06324495..262668cb03a 100644 --- a/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java +++ b/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java @@ -42,6 +42,7 @@ import zipkin.Endpoint; import zipkin.internal.v2.Span; import zipkin.internal.v2.codec.SpanBytesCodec; +import zipkin.internal.v2.codec.SpanBytesEncoder; /** * This compares the speed of the bundled java codec with the approach used in the scala @@ -155,29 +156,39 @@ public byte[] writeClientSpan_thrift_libthrift() throws TException { return serialize(clientSpanLibThrift); } - static final byte[] span2Json = read("/span2.json"); - static final Span span2 = SpanBytesCodec.JSON.decode(span2Json); - static final List tenSpan2s = Collections.nCopies(10, span2); - static final byte[] tenSpan2sJson = SpanBytesCodec.JSON.encodeList(tenSpan2s); + static final byte[] zipkin2Json = read("/zipkin2-client.json"); + static final Span zipkin2 = SpanBytesCodec.JSON_V2.decode(zipkin2Json); + static final List tenSpan2s = Collections.nCopies(10, zipkin2); + static final byte[] tenSpan2sJson = SpanBytesCodec.JSON_V2.encodeList(tenSpan2s); @Benchmark - public Span readClientSpan_json_span2() { - return SpanBytesCodec.JSON.decode(span2Json); + public Span readClientSpan_json_zipkin2() { + return SpanBytesCodec.JSON_V2.decode(zipkin2Json); } @Benchmark - public List readTenClientSpans_json_span2() { - return SpanBytesCodec.JSON.decodeList(tenSpan2sJson); + public List readTenClientSpans_json_zipkin2() { + return SpanBytesCodec.JSON_V2.decodeList(tenSpan2sJson); } @Benchmark - public byte[] writeClientSpan_json_span2() { - return SpanBytesCodec.JSON.encode(span2); + public byte[] writeClientSpan_json_zipkin2() { + return SpanBytesEncoder.JSON_V2.encode(zipkin2); } @Benchmark - public byte[] writeTenClientSpans_json_span2() { - return SpanBytesCodec.JSON.encodeList(tenSpan2s); + public byte[] writeTenClientSpans_json_zipkin2() { + return SpanBytesEncoder.JSON_V2.encodeList(tenSpan2s); + } + + @Benchmark + public byte[] writeClientSpan_json_zipkin2_legacy() { + return SpanBytesEncoder.JSON_V1.encode(zipkin2); + } + + @Benchmark + public byte[] writeTenClientSpans_json_zipkin2_legacy() { + return SpanBytesEncoder.JSON_V1.encodeList(tenSpan2s); } static final byte[] rpcSpanJson = read("/span-rpc.json"); @@ -253,7 +264,7 @@ public byte[] writeRpcV6Span_thrift_libthrift() throws TException { // Convenience main entry-point public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() - .include(".*" + CodecBenchmarks.class.getSimpleName() + ".*ClientSpan.*") + .include(".*" + CodecBenchmarks.class.getSimpleName() + ".*") .build(); new Runner(opt).run(); diff --git a/benchmarks/src/main/resources/span-client.json b/benchmarks/src/main/resources/span-client.json index da8b4b65fb4..6e16745e9da 100644 --- a/benchmarks/src/main/resources/span-client.json +++ b/benchmarks/src/main/resources/span-client.json @@ -16,7 +16,7 @@ }, { "timestamp": 1472470996238000, - "value": "ws", + "value": "foo", "endpoint": { "serviceName": "frontend", "ipv4": "127.0.0.1" @@ -24,7 +24,7 @@ }, { "timestamp": 1472470996403000, - "value": "wr", + "value": "bar", "endpoint": { "serviceName": "frontend", "ipv4": "127.0.0.1" diff --git a/benchmarks/src/main/resources/span2.json b/benchmarks/src/main/resources/zipkin2-client.json similarity index 93% rename from benchmarks/src/main/resources/span2.json rename to benchmarks/src/main/resources/zipkin2-client.json index 45ea81918d4..44df5c06690 100644 --- a/benchmarks/src/main/resources/span2.json +++ b/benchmarks/src/main/resources/zipkin2-client.json @@ -18,11 +18,11 @@ "annotations": [ { "timestamp": 1472470996238000, - "value": "ws" + "value": "foo" }, { "timestamp": 1472470996403000, - "value": "wr" + "value": "bar" } ], "tags": { diff --git a/zipkin-collector/kafka/README.md b/zipkin-collector/kafka/README.md index 92af5f5974d..38b1bb1aa6b 100644 --- a/zipkin-collector/kafka/README.md +++ b/zipkin-collector/kafka/README.md @@ -38,4 +38,4 @@ for (int i = 0; i < count; i++) { ### Legacy encoding Older versions of zipkin accepted a single span per message, as opposed -to a list per message. This practice is deprecated, but still supported. \ No newline at end of file +to a list per message. This practice is deprecated, but still supported. diff --git a/zipkin-collector/kafka/src/test/java/zipkin/collector/kafka/KafkaCollectorTest.java b/zipkin-collector/kafka/src/test/java/zipkin/collector/kafka/KafkaCollectorTest.java index d6f24e9ba31..20cce5eac0c 100644 --- a/zipkin-collector/kafka/src/test/java/zipkin/collector/kafka/KafkaCollectorTest.java +++ b/zipkin-collector/kafka/src/test/java/zipkin/collector/kafka/KafkaCollectorTest.java @@ -32,7 +32,7 @@ import zipkin.collector.kafka.KafkaCollector.Builder; import zipkin.internal.ApplyTimestampAndDuration; import zipkin.internal.V2SpanConverter; -import zipkin.internal.v2.codec.SpanBytesCodec; +import zipkin.internal.v2.codec.SpanBytesEncoder; import zipkin.storage.AsyncSpanConsumer; import zipkin.storage.AsyncSpanStore; import zipkin.storage.SpanStore; @@ -146,7 +146,7 @@ public void messageWithMultipleSpans_json2() throws Exception { ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[1]) ); - byte[] message = SpanBytesCodec.JSON.encodeList(asList( + byte[] message = SpanBytesEncoder.JSON_V2.encodeList(asList( V2SpanConverter.fromSpan(spans.get(0)).get(0), V2SpanConverter.fromSpan(spans.get(1)).get(0) )); diff --git a/zipkin-collector/kafka10/README.md b/zipkin-collector/kafka10/README.md index 8ccbef800de..30126488898 100644 --- a/zipkin-collector/kafka10/README.md +++ b/zipkin-collector/kafka10/README.md @@ -1,15 +1,15 @@ # collector-kafka10 ## KafkaCollector -This collector is implemented as a Kafka consumer supporting Kafka brokers running +This collector is implemented as a Kafka consumer supporting Kafka brokers running version 0.10.0.0 or later. It polls a Kafka topic for messages that contain a list of spans in json or TBinaryProtocol big-endian encoding. These spans are pushed to a span consumer. -For information about running this collector as a module in Zipkin server, see +For information about running this collector as a module in Zipkin server, see [zipkin-autoconfigure/collector-kafka10](../../zipkin-autoconfigure/collector-kafka10/). -When using this collector as a library outside of Zipkin server, +When using this collector as a library outside of Zipkin server, [zipkin.collector.kafka10.KafkaCollector.Builder](src/main/java/zipkin/collector/kafka10/KafkaCollector.java) includes defaults that will operate against a Kafka topic name `zipkin`. @@ -43,4 +43,4 @@ for (int i = 0; i < count; i++) { ### Legacy encoding Older versions of zipkin accepted a single span per message, as opposed -to a list per message. This practice is deprecated, but still supported. \ No newline at end of file +to a list per message. This practice is deprecated, but still supported. diff --git a/zipkin-collector/kafka10/src/test/java/zipkin/collector/kafka10/KafkaCollectorTest.java b/zipkin-collector/kafka10/src/test/java/zipkin/collector/kafka10/KafkaCollectorTest.java index 1461d7002db..9a288bbcade 100644 --- a/zipkin-collector/kafka10/src/test/java/zipkin/collector/kafka10/KafkaCollectorTest.java +++ b/zipkin-collector/kafka10/src/test/java/zipkin/collector/kafka10/KafkaCollectorTest.java @@ -39,7 +39,7 @@ import zipkin.collector.kafka10.KafkaCollector.Builder; import zipkin.internal.ApplyTimestampAndDuration; import zipkin.internal.V2SpanConverter; -import zipkin.internal.v2.codec.SpanBytesCodec; +import zipkin.internal.v2.codec.SpanBytesEncoder; import zipkin.storage.AsyncSpanConsumer; import zipkin.storage.AsyncSpanStore; import zipkin.storage.SpanStore; @@ -200,7 +200,7 @@ public void messageWithMultipleSpans_json2() throws Exception { ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[1]) ); - byte[] message = SpanBytesCodec.JSON.encodeList(asList( + byte[] message = SpanBytesEncoder.JSON_V2.encodeList(asList( V2SpanConverter.fromSpan(spans.get(0)).get(0), V2SpanConverter.fromSpan(spans.get(1)).get(0) )); diff --git a/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java b/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java index 5761d8de2ce..6532a383660 100644 --- a/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java +++ b/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java @@ -132,11 +132,11 @@ MockResponse queryV2(HttpUrl url) throws IOException { return jsonResponse(DependencyLinkBytesCodec.JSON.encodeList(result)); } else if (url.encodedPath().equals("/api/v2/traces")) { List> traces = store2.getTraces(toQueryRequest2(url)).execute(); - return jsonResponse(SpanBytesCodec.JSON.encodeNestedList(traces)); + return jsonResponse(SpanBytesCodec.JSON_V2.encodeNestedList(traces)); } else if (url.encodedPath().startsWith("/api/v2/trace/")) { String traceIdHex = url.encodedPath().replace("/api/v2/trace/", ""); List trace = store2.getTrace(normalizeTraceId(traceIdHex)).execute(); - if (!trace.isEmpty()) return jsonResponse(SpanBytesCodec.JSON.encodeList(trace)); + if (!trace.isEmpty()) return jsonResponse(SpanBytesCodec.JSON_V2.encodeList(trace)); } return new MockResponse().setResponseCode(404); } diff --git a/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java b/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java index 6120206848a..905abe98691 100644 --- a/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java +++ b/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java @@ -68,7 +68,7 @@ public void getTraces_storedViaPostVersion2() throws IOException { ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[1]) ); - byte[] message = SpanBytesCodec.JSON.encodeList(asList( + byte[] message = SpanBytesCodec.JSON_V2.encodeList(asList( V2SpanConverter.fromSpan(spans.get(0)).get(0), V2SpanConverter.fromSpan(spans.get(1)).get(0) )); diff --git a/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanConsumer.java b/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanConsumer.java index edefd685692..903d536c3f8 100644 --- a/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanConsumer.java +++ b/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanConsumer.java @@ -32,7 +32,7 @@ final class HttpV2SpanConsumer implements SpanConsumer { } @Override public zipkin.internal.v2.Call accept(List spans) { - byte[] json = SpanBytesCodec.JSON.encodeList(spans); + byte[] json = SpanBytesCodec.JSON_V2.encodeList(spans); return factory.newCall(new Request.Builder() .url(factory.baseUrl.resolve("/api/v2/spans")) .post(RequestBody.create(MediaType.parse("application/json"), json)).build(), diff --git a/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanStore.java b/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanStore.java index 58e974d2af5..36e4fb1b176 100644 --- a/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanStore.java +++ b/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanStore.java @@ -52,13 +52,13 @@ final class HttpV2SpanStore implements SpanStore { maybeAddQueryParam(url, "lookback", request.lookback()); maybeAddQueryParam(url, "limit", request.limit()); return factory.newCall(new Request.Builder().url(url.build()).build(), - content -> SpanBytesCodec.JSON.decodeNestedList(content.readByteArray())); + content -> SpanBytesCodec.JSON_V2.decodeNestedList(content.readByteArray())); } @Override public Call> getTrace(String traceId) { return factory.newCall(new Request.Builder() .url(factory.baseUrl.resolve("/api/v2/trace/" + Span.normalizeTraceId(traceId))) - .build(), content -> SpanBytesCodec.JSON.decodeList(content.readByteArray())) + .build(), content -> SpanBytesCodec.JSON_V2.decodeList(content.readByteArray())) .handleError(((error, callback) -> { if (error instanceof HttpException && ((HttpException) error).code == 404) { callback.onSuccess(Collections.emptyList()); diff --git a/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java b/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java index e03ef3aebde..c7f590b93c2 100644 --- a/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java +++ b/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java @@ -128,7 +128,7 @@ public String getTraces( .limit(limit).build(); List> traces = storage.v2SpanStore().getTraces(queryRequest).execute(); - return new String(SpanBytesCodec.JSON.encodeNestedList(traces), UTF_8); + return new String(SpanBytesCodec.JSON_V2.encodeNestedList(traces), UTF_8); } @RequestMapping(value = "/trace/{traceIdHex}", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE) @@ -137,7 +137,7 @@ public String getTrace(@PathVariable String traceIdHex, WebRequest request) thro List trace = storage.v2SpanStore().getTrace(traceIdHex).execute(); if (trace.isEmpty()) throw new TraceNotFoundException(traceIdHex); - return new String(SpanBytesCodec.JSON.encodeList(trace), UTF_8); + return new String(SpanBytesCodec.JSON_V2.encodeList(trace), UTF_8); } @ExceptionHandler(Version2StorageNotConfigured.class) diff --git a/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java b/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java index 8086f6dc916..17c16cbd7dc 100644 --- a/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java +++ b/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java @@ -38,7 +38,7 @@ import zipkin.internal.ApplyTimestampAndDuration; import zipkin.internal.V2InMemoryStorage; import zipkin.internal.V2SpanConverter; -import zipkin.internal.v2.codec.SpanBytesCodec; +import zipkin.internal.v2.codec.SpanBytesEncoder; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -93,7 +93,7 @@ public void writeSpans_noContentTypeIsJson() throws Exception { public void writeSpans_version2() throws Exception { Span span = ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[0]); - byte[] message = SpanBytesCodec.JSON.encodeList(asList( + byte[] message = SpanBytesEncoder.JSON_V2.encodeList(asList( V2SpanConverter.fromSpan(span).get(0) )); diff --git a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumer.java b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumer.java index 4834049da00..5c31fa3d067 100644 --- a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumer.java +++ b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumer.java @@ -131,9 +131,9 @@ static byte[] prefixWithTimestampMillisAndQuery(Span span, @Nullable Long timest if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Error indexing query for span: " + span, e); } - return SpanBytesEncoder.JSON.encode(span); + return SpanBytesEncoder.JSON_V2.encode(span); } - byte[] document = SpanBytesEncoder.JSON.encode(span); + byte[] document = SpanBytesEncoder.JSON_V2.encode(span); if (query.rangeEquals(0L, ByteString.of(new byte[] {'{', '}'}))) { return document; } diff --git a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumerTest.java b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumerTest.java index a6bded81f8e..8bacdbf84cc 100644 --- a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumerTest.java +++ b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanConsumerTest.java @@ -123,7 +123,7 @@ public void close() throws IOException { Span span = Span.newBuilder().traceId("20").id("20").name("get") .timestamp(TODAY * 1000).build(); - assertThat(SpanBytesCodec.JSON.decode(prefixWithTimestampMillisAndQuery(span, span.timestamp()))) + assertThat(SpanBytesCodec.JSON_V2.decode(prefixWithTimestampMillisAndQuery(span, span.timestamp()))) .isEqualTo(span); // ignores timestamp_millis field } @@ -144,7 +144,7 @@ public void close() throws IOException { accept(Span.newBuilder().traceId("1").id("1").name("foo").build()); assertThat(es.takeRequest().getBody().readByteString().utf8()) - .contains("\n" + new String(SpanBytesCodec.JSON.encode(span), "UTF-8") + "\n"); + .contains("\n" + new String(SpanBytesCodec.JSON_V2.encode(span), "UTF-8") + "\n"); } @Test public void traceIsSearchableByServerServiceName() throws Exception { diff --git a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/JsonAdaptersTest.java b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/JsonAdaptersTest.java index 46df03f12b9..d6871c23b82 100644 --- a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/JsonAdaptersTest.java +++ b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/JsonAdaptersTest.java @@ -133,7 +133,7 @@ public void span_roundTrip() throws IOException { zipkin.Span span = ApplyTimestampAndDuration.apply(TestObjects.LOTS_OF_SPANS[0]); Span span2 = V2SpanConverter.fromSpan(span).get(0); Buffer bytes = new Buffer(); - bytes.write(SpanBytesEncoder.JSON.encode(span2)); + bytes.write(SpanBytesEncoder.JSON_V2.encode(span2)); assertThat(SPAN_ADAPTER.fromJson(bytes)) .isEqualTo(span2); } @@ -158,7 +158,7 @@ public void span_specialCharsInJson() throws IOException { .build(); Buffer bytes = new Buffer(); - bytes.write(SpanBytesEncoder.JSON.encode(worstSpanInTheWorld)); + bytes.write(SpanBytesEncoder.JSON_V2.encode(worstSpanInTheWorld)); assertThat(SPAN_ADAPTER.fromJson(bytes)) .isEqualTo(worstSpanInTheWorld); } diff --git a/zipkin/src/main/java/zipkin/collector/Collector.java b/zipkin/src/main/java/zipkin/collector/Collector.java index 6d75ae260c9..a84bc4f0490 100644 --- a/zipkin/src/main/java/zipkin/collector/Collector.java +++ b/zipkin/src/main/java/zipkin/collector/Collector.java @@ -108,7 +108,7 @@ public void acceptSpans(byte[] serializedSpans, SpanDecoder decoder, Callback readSpans(byte[] span) { - List span2s = SpanBytesCodec.JSON.decodeList(span); + List span2s = SpanBytesCodec.JSON_V2.decodeList(span); if (span2s.isEmpty()) return Collections.emptyList(); int length = span2s.size(); List result = new ArrayList<>(length); diff --git a/zipkin/src/main/java/zipkin/internal/v2/Span.java b/zipkin/src/main/java/zipkin/internal/v2/Span.java index 5fa1881f3bb..fc1581991e1 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/Span.java +++ b/zipkin/src/main/java/zipkin/internal/v2/Span.java @@ -427,7 +427,7 @@ public Span build() { } @Override public String toString() { - return new String(SpanBytesEncoder.JSON.encode(this), UTF_8); + return new String(SpanBytesEncoder.JSON_V2.encode(this), UTF_8); } /** diff --git a/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesCodec.java b/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesCodec.java index 637b695b7ca..58f9e0c8bec 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesCodec.java +++ b/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesCodec.java @@ -23,25 +23,26 @@ import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import zipkin.internal.v2.internal.JsonCodec; +import zipkin.internal.v2.internal.V2SpanWriter; /** This is separate from {@link SpanBytesEncoder}, as it isn't needed for instrumentation */ public enum SpanBytesCodec implements BytesEncoder, BytesDecoder { /** Corresponds to the Zipkin v2 json format */ - JSON { + JSON_V2 { @Override public Encoding encoding() { return Encoding.JSON; } @Override public int sizeInBytes(Span input) { - return SpanBytesEncoder.JSON.sizeInBytes(input); + return SpanBytesEncoder.JSON_V2.sizeInBytes(input); } @Override public byte[] encode(Span input) { - return SpanBytesEncoder.JSON.encode(input); + return SpanBytesEncoder.JSON_V2.encode(input); } @Override public byte[] encodeList(List input) { - return SpanBytesEncoder.JSON.encodeList(input); + return SpanBytesEncoder.JSON_V2.encodeList(input); } @Override public Span decode(byte[] span) { // ex decode span in dependencies job @@ -53,7 +54,7 @@ public enum SpanBytesCodec implements BytesEncoder, BytesDecoder { } @Override public byte[] encodeNestedList(List> traces) { - return JsonCodec.writeNestedList(SpanBytesEncoder.SPAN_WRITER, traces); + return JsonCodec.writeNestedList(new V2SpanWriter(), traces); } @Override public List> decodeNestedList(byte[] traces) { // ex getTraces diff --git a/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesEncoder.java b/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesEncoder.java index f93a474c0fb..f564b6c758c 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesEncoder.java +++ b/zipkin/src/main/java/zipkin/internal/v2/codec/SpanBytesEncoder.java @@ -13,223 +13,55 @@ */ package zipkin.internal.v2.codec; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import zipkin.internal.v2.Annotation; -import zipkin.internal.v2.Endpoint; +import javax.annotation.concurrent.Immutable; import zipkin.internal.v2.Span; import zipkin.internal.v2.internal.Buffer; import zipkin.internal.v2.internal.JsonCodec; - -import static zipkin.internal.v2.internal.Buffer.asciiSizeInBytes; -import static zipkin.internal.v2.internal.JsonEscaper.jsonEscape; -import static zipkin.internal.v2.internal.JsonEscaper.jsonEscapedSizeInBytes; +import zipkin.internal.v2.internal.V1SpanWriter; +import zipkin.internal.v2.internal.V2SpanWriter; /** Limited interface needed by those writing span reporters */ +@Immutable public enum SpanBytesEncoder implements BytesEncoder { - /** Corresponds to the Zipkin v2 json format */ - JSON { + /** Corresponds to the Zipkin v1 json format (with tags as binary annotations) */ + JSON_V1 { + final Buffer.Writer writer = new V1SpanWriter(); + @Override public Encoding encoding() { return Encoding.JSON; } @Override public int sizeInBytes(Span input) { - return SPAN_WRITER.sizeInBytes(input); + return writer.sizeInBytes(input); } @Override public byte[] encode(Span span) { - return JsonCodec.write(SPAN_WRITER, span); + return JsonCodec.write(writer, span); } @Override public byte[] encodeList(List spans) { - return JsonCodec.writeList(SPAN_WRITER, spans); - } - }; - - static final Buffer.Writer ENDPOINT_WRITER = new Buffer.Writer() { - @Override public int sizeInBytes(Endpoint value) { - int sizeInBytes = 1; // { - if (value.serviceName() != null) { - sizeInBytes += 16; // "serviceName":"" - sizeInBytes += jsonEscapedSizeInBytes(value.serviceName()); - } - if (value.ipv4() != null) { - if (sizeInBytes != 1) sizeInBytes++; // , - sizeInBytes += 9; // "ipv4":"" - sizeInBytes += value.ipv4().length(); - } - if (value.ipv6() != null) { - if (sizeInBytes != 1) sizeInBytes++; // , - sizeInBytes += 9; // "ipv6":"" - sizeInBytes += value.ipv6().length(); - } - if (value.port() != null) { - if (sizeInBytes != 1) sizeInBytes++; // , - sizeInBytes += 7; // "port": - sizeInBytes += asciiSizeInBytes(value.port()); - } - return ++sizeInBytes; // } - } - - @Override public void write(Endpoint value, Buffer b) { - b.writeByte('{'); - boolean wroteField = false; - if (value.serviceName() != null) { - b.writeAscii("\"serviceName\":\""); - b.writeUtf8(jsonEscape(value.serviceName())).writeByte('"'); - wroteField = true; - } - if (value.ipv4() != null) { - if (wroteField) b.writeByte(','); - b.writeAscii("\"ipv4\":\""); - b.writeAscii(value.ipv4()).writeByte('"'); - wroteField = true; - } - if (value.ipv6() != null) { - if (wroteField) b.writeByte(','); - b.writeAscii("\"ipv6\":\""); - b.writeAscii(value.ipv6()).writeByte('"'); - wroteField = true; - } - if (value.port() != null) { - if (wroteField) b.writeByte(','); - b.writeAscii("\"port\":").writeAscii(value.port()); - } - b.writeByte('}'); - } - }; - - static final Buffer.Writer ANNOTATION_WRITER = new Buffer.Writer() { - @Override public int sizeInBytes(Annotation value) { - int sizeInBytes = 25; // {"timestamp":,"value":""} - sizeInBytes += asciiSizeInBytes(value.timestamp()); - sizeInBytes += jsonEscapedSizeInBytes(value.value()); - return sizeInBytes; + return JsonCodec.writeList(writer, spans); } + }, + /** Corresponds to the Zipkin v2 json format */ + JSON_V2 { + final Buffer.Writer writer = new V2SpanWriter(); - @Override public void write(Annotation value, Buffer b) { - b.writeAscii("{\"timestamp\":").writeAscii(value.timestamp()); - b.writeAscii(",\"value\":\"").writeUtf8(jsonEscape(value.value())).writeAscii("\"}"); + @Override public Encoding encoding() { + return Encoding.JSON; } - }; - static final Buffer.Writer SPAN_WRITER = new Buffer.Writer() { - @Override public int sizeInBytes(Span value) { - int sizeInBytes = 13; // {"traceId":"" - sizeInBytes += value.traceId().length(); - if (value.parentId() != null) { - sizeInBytes += 30; // ,"parentId":"0123456789abcdef" - } - sizeInBytes += 24; // ,"id":"0123456789abcdef" - if (value.kind() != null) { - sizeInBytes += 10; // ,"kind":"" - sizeInBytes += value.kind().name().length(); - } - if (value.name() != null) { - sizeInBytes += 10; // ,"name":"" - sizeInBytes += jsonEscapedSizeInBytes(value.name()); - } - if (value.timestamp() != null) { - sizeInBytes += 13; // ,"timestamp": - sizeInBytes += asciiSizeInBytes(value.timestamp()); - } - if (value.duration() != null) { - sizeInBytes += 12; // ,"duration": - sizeInBytes += asciiSizeInBytes(value.duration()); - } - if (value.localEndpoint() != null) { - sizeInBytes += 17; // ,"localEndpoint": - sizeInBytes += ENDPOINT_WRITER.sizeInBytes(value.localEndpoint()); - } - if (value.remoteEndpoint() != null) { - sizeInBytes += 18; // ,"remoteEndpoint": - sizeInBytes += ENDPOINT_WRITER.sizeInBytes(value.remoteEndpoint()); - } - if (!value.annotations().isEmpty()) { - sizeInBytes += 17; // ,"annotations":[] - int length = value.annotations().size(); - if (length > 1) sizeInBytes += length - 1; // comma to join elements - for (int i = 0; i < length; i++) { - sizeInBytes += ANNOTATION_WRITER.sizeInBytes(value.annotations().get(i)); - } - } - if (!value.tags().isEmpty()) { - sizeInBytes += 10; // ,"tags":{} - int tagCount = value.tags().size(); - if (tagCount > 1) sizeInBytes += tagCount - 1; // comma to join elements - for (Map.Entry entry : value.tags().entrySet()) { - sizeInBytes += 5; // "":"" - sizeInBytes += jsonEscapedSizeInBytes(entry.getKey()); - sizeInBytes += jsonEscapedSizeInBytes(entry.getValue()); - } - } - if (Boolean.TRUE.equals(value.debug())) { - sizeInBytes += 13; // ,"debug":true - } - if (Boolean.TRUE.equals(value.shared())) { - sizeInBytes += 14; // ,"shared":true - } - return ++sizeInBytes; // } + @Override public int sizeInBytes(Span input) { + return writer.sizeInBytes(input); } - @Override public void write(Span value, Buffer b) { - b.writeAscii("{\"traceId\":\"").writeAscii(value.traceId()).writeByte('"'); - if (value.parentId() != null) { - b.writeAscii(",\"parentId\":\"").writeAscii(value.parentId()).writeByte('"'); - } - b.writeAscii(",\"id\":\"").writeAscii(value.id()).writeByte('"'); - if (value.kind() != null) { - b.writeAscii(",\"kind\":\"").writeAscii(value.kind().toString()).writeByte('"'); - } - if (value.name() != null) { - b.writeAscii(",\"name\":\"").writeUtf8(jsonEscape(value.name())).writeByte('"'); - } - if (value.timestamp() != null) { - b.writeAscii(",\"timestamp\":").writeAscii(value.timestamp()); - } - if (value.duration() != null) { - b.writeAscii(",\"duration\":").writeAscii(value.duration()); - } - if (value.localEndpoint() != null) { - b.writeAscii(",\"localEndpoint\":"); - ENDPOINT_WRITER.write(value.localEndpoint(), b); - } - if (value.remoteEndpoint() != null) { - b.writeAscii(",\"remoteEndpoint\":"); - ENDPOINT_WRITER.write(value.remoteEndpoint(), b); - } - if (!value.annotations().isEmpty()) { - b.writeAscii(",\"annotations\":"); - b.writeByte('['); - for (int i = 0, length = value.annotations().size(); i < length; ) { - ANNOTATION_WRITER.write(value.annotations().get(i++), b); - if (i < length) b.writeByte(','); - } - b.writeByte(']'); - } - if (!value.tags().isEmpty()) { - b.writeAscii(",\"tags\":{"); - Iterator> i = value.tags().entrySet().iterator(); - while (i.hasNext()) { - Map.Entry entry = i.next(); - b.writeByte('"').writeUtf8(jsonEscape(entry.getKey())).writeAscii("\":\""); - b.writeUtf8(jsonEscape(entry.getValue())).writeByte('"'); - if (i.hasNext()) b.writeByte(','); - } - b.writeByte('}'); - } - if (Boolean.TRUE.equals(value.debug())) { - b.writeAscii(",\"debug\":true"); - } - if (Boolean.TRUE.equals(value.shared())) { - b.writeAscii(",\"shared\":true"); - } - b.writeByte('}'); + @Override public byte[] encode(Span span) { + return JsonCodec.write(writer, span); } - @Override public String toString() { - return "Span"; + @Override public byte[] encodeList(List spans) { + return JsonCodec.writeList(writer, spans); } - }; + } } diff --git a/zipkin/src/main/java/zipkin/internal/v2/internal/Buffer.java b/zipkin/src/main/java/zipkin/internal/v2/internal/Buffer.java index d44c95e6d29..6e443fdb645 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/internal/Buffer.java +++ b/zipkin/src/main/java/zipkin/internal/v2/internal/Buffer.java @@ -14,20 +14,21 @@ package zipkin.internal.v2.internal; import java.nio.charset.Charset; +import javax.annotation.concurrent.Immutable; public final class Buffer { static final Charset UTF_8 = Charset.forName("UTF-8"); - public interface Writer { + @Immutable public interface Writer { int sizeInBytes(T value); void write(T value, Buffer buffer); } private final byte[] buf; - private int pos; + int pos; // visible for testing - Buffer(int size) { + public Buffer(int size) { buf = new byte[size]; } @@ -151,7 +152,7 @@ public Buffer writeAscii(long v) { static final byte[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; - byte[] toByteArray() { + public byte[] toByteArray() { //assert pos == buf.length; return buf; } diff --git a/zipkin/src/main/java/zipkin/internal/v2/internal/JsonEscaper.java b/zipkin/src/main/java/zipkin/internal/v2/internal/JsonEscaper.java index c087c9b60ee..c4437c3dd29 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/internal/JsonEscaper.java +++ b/zipkin/src/main/java/zipkin/internal/v2/internal/JsonEscaper.java @@ -16,6 +16,7 @@ public final class JsonEscaper { /** Exposed for ElasticSearch HttpBulkIndexer */ public static String jsonEscape(String v) { + if (v.isEmpty()) return v; int afterReplacement = 0; int length = v.length(); StringBuilder builder = null; diff --git a/zipkin/src/main/java/zipkin/internal/v2/internal/V1SpanWriter.java b/zipkin/src/main/java/zipkin/internal/v2/internal/V1SpanWriter.java new file mode 100644 index 00000000000..3f78368f308 --- /dev/null +++ b/zipkin/src/main/java/zipkin/internal/v2/internal/V1SpanWriter.java @@ -0,0 +1,273 @@ +/** + * Copyright 2015-2017 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.internal.v2.internal; + +import java.util.Iterator; +import java.util.Map; +import javax.annotation.Nullable; +import zipkin.Constants; +import zipkin.internal.v2.Annotation; +import zipkin.internal.v2.Endpoint; +import zipkin.internal.v2.Span; + +import static zipkin.internal.v2.internal.Buffer.asciiSizeInBytes; +import static zipkin.internal.v2.internal.JsonEscaper.jsonEscape; +import static zipkin.internal.v2.internal.JsonEscaper.jsonEscapedSizeInBytes; +import static zipkin.internal.v2.internal.V2SpanWriter.annotationSizeInBytes; +import static zipkin.internal.v2.internal.V2SpanWriter.endpointSizeInBytes; +import static zipkin.internal.v2.internal.V2SpanWriter.writeAnnotation; +import static zipkin.internal.v2.internal.V2SpanWriter.writeEndpoint; + +public final class V1SpanWriter implements Buffer.Writer { + @Override public int sizeInBytes(Span value) { + Parsed parsed = parse(value); + + Integer endpointSize; + if (value.localEndpoint() != null) { + endpointSize = endpointSizeInBytes(value.localEndpoint()); + if (value.localServiceName() == null) { + endpointSize += 17; // "serviceName":"", + } + } else { + endpointSize = null; + } + int sizeInBytes = 13; // {"traceId":"" + sizeInBytes += value.traceId().length(); + if (value.parentId() != null) { + sizeInBytes += 30; // ,"parentId":"0123456789abcdef" + } + sizeInBytes += 24; // ,"id":"0123456789abcdef" + sizeInBytes += 10; // ,"name":"" + if (value.name() != null) { + sizeInBytes += jsonEscapedSizeInBytes(value.name()); + } + if (Boolean.TRUE.equals(value.shared()) && "sr".equals(parsed.begin)) { + // don't report server-side timestamp on shared or incomplete spans + } else { + if (value.timestamp() != null) { + sizeInBytes += 13; // ,"timestamp": + sizeInBytes += asciiSizeInBytes(value.timestamp()); + } + if (value.duration() != null) { + sizeInBytes += 12; // ,"duration": + sizeInBytes += asciiSizeInBytes(value.duration()); + } + } + + int annotationCount = value.annotations().size(); + + if (parsed.startTs != null && parsed.begin != null) { + annotationCount++; + sizeInBytes += coreAnnotationSizeInBytes(parsed.startTs, endpointSize); + } + + if (parsed.endTs != null && parsed.end != null) { + annotationCount++; + sizeInBytes += coreAnnotationSizeInBytes(parsed.endTs, endpointSize); + } + + if (annotationCount > 0) { + sizeInBytes += 17; // ,"annotations":[] + if (annotationCount > 1) sizeInBytes += annotationCount - 1; // comma to join elements + for (int i = 0, length = value.annotations().size(); i < length; i++) { + sizeInBytes += annotationSizeInBytes(value.annotations().get(i), endpointSize); + } + } + + int binaryAnnotationCount = value.tags().size(); + + if (parsed.remoteEndpointType != null && value.remoteEndpoint() != null) { + binaryAnnotationCount++; + sizeInBytes += 37; // {"key":"NN","value":true,"endpoint":} + sizeInBytes += endpointSizeInBytes(value.remoteEndpoint()); + if (value.localServiceName() == null) { + sizeInBytes += 17; // "serviceName":"", + } + } + + if (binaryAnnotationCount > 0) { + sizeInBytes += 23; // ,"binaryAnnotations":[] + if (binaryAnnotationCount > 1) { + sizeInBytes += binaryAnnotationCount - 1; // comma to join elements + } + for (Map.Entry tag : value.tags().entrySet()) { + sizeInBytes += binaryAnnotationSizeInBytes(tag.getKey(), tag.getValue(), endpointSize); + } + } + if (Boolean.TRUE.equals(value.debug())) { + sizeInBytes += 13; // ,"debug":true + } + return ++sizeInBytes; // } + } + + @Override public void write(Span value, Buffer b) { + Parsed parsed = parse(value); + byte[] endpointBytes = legacyEndpointBytes(value.localEndpoint()); + b.writeAscii("{\"traceId\":\"").writeAscii(value.traceId()).writeByte('"'); + if (value.parentId() != null) { + b.writeAscii(",\"parentId\":\"").writeAscii(value.parentId()).writeByte('"'); + } + b.writeAscii(",\"id\":\"").writeAscii(value.id()).writeByte('"'); + b.writeAscii(",\"name\":\""); + if (value.name() != null) b.writeUtf8(jsonEscape(value.name())); + b.writeByte('"'); + + if (Boolean.TRUE.equals(value.shared()) && "sr".equals(parsed.begin)) { + // don't report server-side timestamp on shared or incomplete spans + } else { + if (value.timestamp() != null) { + b.writeAscii(",\"timestamp\":").writeAscii(value.timestamp()); + } + if (value.duration() != null) { + b.writeAscii(",\"duration\":").writeAscii(value.duration()); + } + } + + int annotationCount = value.annotations().size(); + boolean beginAnnotation = parsed.startTs != null && parsed.begin != null; + boolean endAnnotation = parsed.endTs != null && parsed.end != null; + if (annotationCount > 0) { + int length = value.annotations().size(); + b.writeAscii(",\"annotations\":["); + if (beginAnnotation) { + writeAnnotation(Annotation.create(parsed.startTs, parsed.begin), endpointBytes, b); + if (length > 0) b.writeByte(','); + } + for (int i = 0; i < length; ) { + writeAnnotation(value.annotations().get(i++), endpointBytes, b); + if (i < length) b.writeByte(','); + } + if (endAnnotation) { + if (length > 0) b.writeByte(','); + writeAnnotation(Annotation.create(parsed.endTs, parsed.end), endpointBytes, b); + } + b.writeByte(']'); + } + int binaryAnnotationCount = value.tags().size(); + + boolean hasRemoteEndpoint = parsed.remoteEndpointType != null && value.remoteEndpoint() != null; + if (hasRemoteEndpoint) binaryAnnotationCount++; + if (binaryAnnotationCount > 0) { + b.writeAscii(",\"binaryAnnotations\":["); + Iterator> i = value.tags().entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = i.next(); + writeBinaryAnnotation(entry.getKey(), entry.getValue(), endpointBytes, b); + if (i.hasNext()) b.writeByte(','); + } + if (hasRemoteEndpoint) { + if (!value.tags().isEmpty()) b.writeByte(','); + b.writeAscii("{\"key\":\"").writeAscii(parsed.remoteEndpointType); + b.writeAscii("\",\"value\":true,\"endpoint\":"); + b.write(legacyEndpointBytes(value.remoteEndpoint())); + b.writeByte('}'); + } + b.writeByte(']'); + } + if (Boolean.TRUE.equals(value.debug())) { + b.writeAscii(",\"debug\":true"); + } + b.writeByte('}'); + } + + @Override public String toString() { + return "Span"; + } + + static byte[] legacyEndpointBytes(@Nullable Endpoint localEndpoint) { + if (localEndpoint == null) return null; + Buffer buffer = new Buffer(endpointSizeInBytes(localEndpoint)); + writeEndpoint(localEndpoint, buffer); + byte[] endpointBytes = buffer.toByteArray(); + if (localEndpoint.serviceName() != null) return endpointBytes; + byte[] newSpanBytes = new byte[17 /* {"serviceName":"" */ + endpointBytes.length]; + System.arraycopy("{\"serviceName\":\"\"".getBytes(), 0, newSpanBytes, 0, 17); + newSpanBytes[17] = ','; + System.arraycopy(endpointBytes, 1, newSpanBytes, 18, endpointBytes.length - 1); + return newSpanBytes; + } + + static int binaryAnnotationSizeInBytes(String key, String value, @Nullable Integer endpointSize) { + int sizeInBytes = 21; // {"key":"","value":""} + sizeInBytes += jsonEscapedSizeInBytes(key); + sizeInBytes += jsonEscapedSizeInBytes(value); + if (endpointSize != null) { + sizeInBytes += 12; // ,"endpoint": + sizeInBytes += endpointSize; + } + return sizeInBytes; + } + + static void writeBinaryAnnotation(String key, String value, @Nullable byte[] endpoint, Buffer b) { + b.writeAscii("{\"key\":\"").writeUtf8(jsonEscape(key)); + b.writeAscii("\",\"value\":\"").writeUtf8(jsonEscape(value)).writeByte('"'); + if (endpoint != null) b.writeAscii(",\"endpoint\":").write(endpoint); + b.writeAscii("}"); + } + + static class Parsed { + Long startTs = null, endTs = null; + String begin = null, end = null; + String remoteEndpointType = null; + } + + static Parsed parse(Span in) { + Parsed parsed = new Parsed(); + parsed.startTs = in.timestamp(); + parsed.endTs = in.timestamp() != null && in.duration() != null + ? in.timestamp() + in.duration() : null; + + if (in.kind() != null) { + switch (in.kind()) { + case CLIENT: + parsed.remoteEndpointType = Constants.SERVER_ADDR; + parsed.begin = "cs"; + parsed.end = "cr"; + break; + case SERVER: + parsed.remoteEndpointType = Constants.CLIENT_ADDR; + parsed.begin = "sr"; + parsed.end = "ss"; + break; + case PRODUCER: + parsed.remoteEndpointType = Constants.MESSAGE_ADDR; + parsed.begin = "ms"; + parsed.end = "ws"; + break; + case CONSUMER: + parsed.remoteEndpointType = Constants.MESSAGE_ADDR; + if (parsed.endTs != null) { + parsed.begin = "wr"; + parsed.end = "mr"; + } else { + parsed.begin = "mr"; + } + break; + default: + throw new AssertionError("update kind mapping"); + } + } + return parsed; + } + + static int coreAnnotationSizeInBytes(long timestamp, @Nullable Integer endpointSizeInBytes) { + int sizeInBytes = 27; // {"timestamp":,"value":"??"} + sizeInBytes += asciiSizeInBytes(timestamp); + if (endpointSizeInBytes != null) { + sizeInBytes += 12; // ,"endpoint": + sizeInBytes += endpointSizeInBytes; + } + return sizeInBytes; + } +} diff --git a/zipkin/src/main/java/zipkin/internal/v2/internal/V2SpanWriter.java b/zipkin/src/main/java/zipkin/internal/v2/internal/V2SpanWriter.java new file mode 100644 index 00000000000..aa30c1f516f --- /dev/null +++ b/zipkin/src/main/java/zipkin/internal/v2/internal/V2SpanWriter.java @@ -0,0 +1,213 @@ +/** + * Copyright 2015-2017 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.internal.v2.internal; + +import java.util.Iterator; +import java.util.Map; +import javax.annotation.Nullable; +import zipkin.internal.v2.Annotation; +import zipkin.internal.v2.Endpoint; +import zipkin.internal.v2.Span; + +import static zipkin.internal.v2.internal.Buffer.asciiSizeInBytes; +import static zipkin.internal.v2.internal.JsonEscaper.jsonEscape; +import static zipkin.internal.v2.internal.JsonEscaper.jsonEscapedSizeInBytes; + +public final class V2SpanWriter implements Buffer.Writer { + @Override public int sizeInBytes(Span value) { + int sizeInBytes = 13; // {"traceId":"" + sizeInBytes += value.traceId().length(); + if (value.parentId() != null) { + sizeInBytes += 30; // ,"parentId":"0123456789abcdef" + } + sizeInBytes += 24; // ,"id":"0123456789abcdef" + if (value.kind() != null) { + sizeInBytes += 10; // ,"kind":"" + sizeInBytes += value.kind().name().length(); + } + if (value.name() != null) { + sizeInBytes += 10; // ,"name":"" + sizeInBytes += jsonEscapedSizeInBytes(value.name()); + } + if (value.timestamp() != null) { + sizeInBytes += 13; // ,"timestamp": + sizeInBytes += asciiSizeInBytes(value.timestamp()); + } + if (value.duration() != null) { + sizeInBytes += 12; // ,"duration": + sizeInBytes += asciiSizeInBytes(value.duration()); + } + if (value.localEndpoint() != null) { + sizeInBytes += 17; // ,"localEndpoint": + sizeInBytes += endpointSizeInBytes(value.localEndpoint()); + } + if (value.remoteEndpoint() != null) { + sizeInBytes += 18; // ,"remoteEndpoint": + sizeInBytes += endpointSizeInBytes(value.remoteEndpoint()); + } + if (!value.annotations().isEmpty()) { + sizeInBytes += 17; // ,"annotations":[] + int length = value.annotations().size(); + if (length > 1) sizeInBytes += length - 1; // comma to join elements + for (int i = 0; i < length; i++) { + sizeInBytes += annotationSizeInBytes(value.annotations().get(i), null); + } + } + if (!value.tags().isEmpty()) { + sizeInBytes += 10; // ,"tags":{} + int tagCount = value.tags().size(); + if (tagCount > 1) sizeInBytes += tagCount - 1; // comma to join elements + for (Map.Entry entry : value.tags().entrySet()) { + sizeInBytes += 5; // "":"" + sizeInBytes += jsonEscapedSizeInBytes(entry.getKey()); + sizeInBytes += jsonEscapedSizeInBytes(entry.getValue()); + } + } + if (Boolean.TRUE.equals(value.debug())) { + sizeInBytes += 13; // ,"debug":true + } + if (Boolean.TRUE.equals(value.shared())) { + sizeInBytes += 14; // ,"shared":true + } + return ++sizeInBytes; // } + } + + @Override public void write(Span value, Buffer b) { + b.writeAscii("{\"traceId\":\"").writeAscii(value.traceId()).writeByte('"'); + if (value.parentId() != null) { + b.writeAscii(",\"parentId\":\"").writeAscii(value.parentId()).writeByte('"'); + } + b.writeAscii(",\"id\":\"").writeAscii(value.id()).writeByte('"'); + if (value.kind() != null) { + b.writeAscii(",\"kind\":\"").writeAscii(value.kind().toString()).writeByte('"'); + } + if (value.name() != null) { + b.writeAscii(",\"name\":\"").writeUtf8(jsonEscape(value.name())).writeByte('"'); + } + if (value.timestamp() != null) { + b.writeAscii(",\"timestamp\":").writeAscii(value.timestamp()); + } + if (value.duration() != null) { + b.writeAscii(",\"duration\":").writeAscii(value.duration()); + } + if (value.localEndpoint() != null) { + b.writeAscii(",\"localEndpoint\":"); + writeEndpoint(value.localEndpoint(), b); + } + if (value.remoteEndpoint() != null) { + b.writeAscii(",\"remoteEndpoint\":"); + writeEndpoint(value.remoteEndpoint(), b); + } + if (!value.annotations().isEmpty()) { + b.writeAscii(",\"annotations\":"); + b.writeByte('['); + for (int i = 0, length = value.annotations().size(); i < length; ) { + writeAnnotation(value.annotations().get(i++), null, b); + if (i < length) b.writeByte(','); + } + b.writeByte(']'); + } + if (!value.tags().isEmpty()) { + b.writeAscii(",\"tags\":{"); + Iterator> i = value.tags().entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = i.next(); + b.writeByte('"').writeUtf8(jsonEscape(entry.getKey())).writeAscii("\":\""); + b.writeUtf8(jsonEscape(entry.getValue())).writeByte('"'); + if (i.hasNext()) b.writeByte(','); + } + b.writeByte('}'); + } + if (Boolean.TRUE.equals(value.debug())) { + b.writeAscii(",\"debug\":true"); + } + if (Boolean.TRUE.equals(value.shared())) { + b.writeAscii(",\"shared\":true"); + } + b.writeByte('}'); + } + + @Override public String toString() { + return "Span"; + } + + static int endpointSizeInBytes(Endpoint value) { + int sizeInBytes = 1; // { + if (value.serviceName() != null) { + sizeInBytes += 16; // "serviceName":"" + sizeInBytes += jsonEscapedSizeInBytes(value.serviceName()); + } + if (value.ipv4() != null) { + if (sizeInBytes != 1) sizeInBytes++; // , + sizeInBytes += 9; // "ipv4":"" + sizeInBytes += value.ipv4().length(); + } + if (value.ipv6() != null) { + if (sizeInBytes != 1) sizeInBytes++; // , + sizeInBytes += 9; // "ipv6":"" + sizeInBytes += value.ipv6().length(); + } + if (value.port() != null) { + if (sizeInBytes != 1) sizeInBytes++; // , + sizeInBytes += 7; // "port": + sizeInBytes += asciiSizeInBytes(value.port()); + } + return ++sizeInBytes; // } + } + + static void writeEndpoint(Endpoint value, Buffer b) { + b.writeByte('{'); + boolean wroteField = false; + if (value.serviceName() != null) { + b.writeAscii("\"serviceName\":\""); + b.writeUtf8(jsonEscape(value.serviceName())).writeByte('"'); + wroteField = true; + } + if (value.ipv4() != null) { + if (wroteField) b.writeByte(','); + b.writeAscii("\"ipv4\":\""); + b.writeAscii(value.ipv4()).writeByte('"'); + wroteField = true; + } + if (value.ipv6() != null) { + if (wroteField) b.writeByte(','); + b.writeAscii("\"ipv6\":\""); + b.writeAscii(value.ipv6()).writeByte('"'); + wroteField = true; + } + if (value.port() != null) { + if (wroteField) b.writeByte(','); + b.writeAscii("\"port\":").writeAscii(value.port()); + } + b.writeByte('}'); + } + + static int annotationSizeInBytes(Annotation value, @Nullable Integer endpointSizeInBytes) { + int sizeInBytes = 25; // {"timestamp":,"value":""} + sizeInBytes += asciiSizeInBytes(value.timestamp()); + sizeInBytes += jsonEscapedSizeInBytes(value.value()); + if (endpointSizeInBytes != null) { + sizeInBytes += 12; // ,"endpoint": + sizeInBytes += endpointSizeInBytes; + } + return sizeInBytes; + } + + static void writeAnnotation(Annotation value, @Nullable byte[] endpointBytes, Buffer b) { + b.writeAscii("{\"timestamp\":").writeAscii(value.timestamp()); + b.writeAscii(",\"value\":\"").writeUtf8(jsonEscape(value.value())).writeByte('"'); + if (endpointBytes != null) b.writeAscii(",\"endpoint\":").write(endpointBytes); + b.writeByte('}'); + } +} diff --git a/zipkin/src/test/java/zipkin/collector/CollectorTest.java b/zipkin/src/test/java/zipkin/collector/CollectorTest.java index abc8265d5b2..1c6b2bf022a 100644 --- a/zipkin/src/test/java/zipkin/collector/CollectorTest.java +++ b/zipkin/src/test/java/zipkin/collector/CollectorTest.java @@ -84,7 +84,7 @@ public class CollectorTest { } @Test public void convertsSpan2Format() { - byte[] bytes = SpanBytesEncoder.JSON.encodeList(asList(span2_1)); + byte[] bytes = SpanBytesEncoder.JSON_V2.encodeList(asList(span2_1)); collector.acceptSpans(bytes, SpanDecoder.DETECTING_DECODER, NOOP); verify(collector).acceptSpans(bytes, SpanDecoder.DETECTING_DECODER, NOOP); @@ -106,7 +106,7 @@ abstract class WithSpan2 extends V2StorageComponent implements zipkin.storage.St collector = spy(Collector.builder(Collector.class) .storage(storage).build()); - byte[] bytes = SpanBytesEncoder.JSON.encodeList(asList(span2_1)); + byte[] bytes = SpanBytesEncoder.JSON_V2.encodeList(asList(span2_1)); collector.acceptSpans(bytes, SpanDecoder.DETECTING_DECODER, NOOP); verify(collector, never()).isSampled(any(zipkin.Span.class)); // skips v1 processing diff --git a/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java b/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java index a03051cf36a..b4eb7ec6894 100644 --- a/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java +++ b/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java @@ -69,15 +69,15 @@ public class DetectingSpanDecoderTest { /** Single-element reads were for legacy non-list encoding. Don't add new code that does this */ @Test(expected = UnsupportedOperationException.class) public void readSpan_json2() { - decoder.readSpan(SpanBytesEncoder.JSON.encode(span2_1)); + decoder.readSpan(SpanBytesEncoder.JSON_V2.encode(span2_1)); } @Test(expected = IllegalArgumentException.class) public void readSpans_json2_not_list() { - decoder.readSpans(SpanBytesEncoder.JSON.encode(span2_1)); + decoder.readSpans(SpanBytesEncoder.JSON_V2.encode(span2_1)); } @Test public void readSpans_json2() { - byte[] message = SpanBytesEncoder.JSON.encodeList(asList(span2_1, span2_2)); + byte[] message = SpanBytesEncoder.JSON_V2.encodeList(asList(span2_1, span2_2)); assertThat(decoder.readSpans(message)) .containsExactly(span1, span2); diff --git a/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java b/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java index e79dfdf7851..22cc6cbb33b 100644 --- a/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java +++ b/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java @@ -31,11 +31,11 @@ public class V2JsonSpanDecoderTest { SpanDecoder decoder = new V2JsonSpanDecoder(); @Test(expected = UnsupportedOperationException.class) public void readSpan() { - decoder.readSpan(SpanBytesEncoder.JSON.encode(span2_1)); + decoder.readSpan(SpanBytesEncoder.JSON_V2.encode(span2_1)); } @Test public void readSpans() { - byte[] message = SpanBytesEncoder.JSON.encodeList(asList(span2_1, span2_2)); + byte[] message = SpanBytesEncoder.JSON_V2.encodeList(asList(span2_1, span2_2)); assertThat(decoder.readSpans(message)) .containsExactly(span1, span2); diff --git a/zipkin/src/test/java/zipkin/internal/v2/codec/SpanJsonSpanAdaptersTest.java b/zipkin/src/test/java/zipkin/internal/v2/codec/SpanBytesEncoderTest.java similarity index 77% rename from zipkin/src/test/java/zipkin/internal/v2/codec/SpanJsonSpanAdaptersTest.java rename to zipkin/src/test/java/zipkin/internal/v2/codec/SpanBytesEncoderTest.java index 98e967f86da..588ffcdb141 100644 --- a/zipkin/src/test/java/zipkin/internal/v2/codec/SpanJsonSpanAdaptersTest.java +++ b/zipkin/src/test/java/zipkin/internal/v2/codec/SpanBytesEncoderTest.java @@ -29,7 +29,7 @@ import static zipkin.internal.Util.UTF_8; import static zipkin.internal.V2SpanConverter.toEndpoint; -public class SpanJsonSpanAdaptersTest { +public class SpanBytesEncoderTest { Endpoint frontend = Endpoint.create("frontend", 127 << 24 | 1); Endpoint backend = Endpoint.builder() .serviceName("backend") @@ -56,36 +56,24 @@ public class SpanJsonSpanAdaptersTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void spanRoundTrip() throws IOException { - assertThat(SpanBytesCodec.JSON.decode(SpanBytesEncoder.JSON.encode(span))) + assertThat(SpanBytesCodec.JSON_V2.decode(SpanBytesEncoder.JSON_V2.encode(span))) .isEqualTo(span); } - @Test public void sizeInBytes() throws IOException { - assertThat(SpanBytesEncoder.SPAN_WRITER.sizeInBytes(span)) - .isEqualTo(SpanBytesEncoder.JSON.encode(span).length); - } - @Test public void spanRoundTrip_64bitTraceId() throws IOException { span = span.toBuilder().traceId(span.traceId().substring(16)).build(); - assertThat(SpanBytesCodec.JSON.decode(SpanBytesEncoder.JSON.encode(span))) + assertThat(SpanBytesCodec.JSON_V2.decode(SpanBytesEncoder.JSON_V2.encode(span))) .isEqualTo(span); } @Test public void spanRoundTrip_shared() throws IOException { span = span.toBuilder().shared(true).build(); - assertThat(SpanBytesCodec.JSON.decode(SpanBytesEncoder.JSON.encode(span))) + assertThat(SpanBytesCodec.JSON_V2.decode(SpanBytesEncoder.JSON_V2.encode(span))) .isEqualTo(span); } - @Test public void sizeInBytes_64bitTraceId() throws IOException { - span = span.toBuilder().traceId(span.traceId().substring(16)).build(); - - assertThat(SpanBytesEncoder.SPAN_WRITER.sizeInBytes(span)) - .isEqualTo(SpanBytesEncoder.JSON.encode(span).length); - } - /** * This isn't a test of what we "should" accept as a span, rather that characters that trip-up * json don't fail in codec. @@ -101,7 +89,7 @@ public class SpanJsonSpanAdaptersTest { .putTag("\"foo", "Database error: ORA-00942:\u2028 and \u2029 table or view does not exist\n") .build(); - assertThat(SpanBytesCodec.JSON.decode(SpanBytesEncoder.JSON.encode(worstSpanInTheWorld))) + assertThat(SpanBytesCodec.JSON_V2.decode(SpanBytesEncoder.JSON_V2.encode(worstSpanInTheWorld))) .isEqualTo(worstSpanInTheWorld); } @@ -115,14 +103,14 @@ public class SpanJsonSpanAdaptersTest { + " \"id\": \"6b221d5bc9e6496c\"\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnEmpty_inputSpans() throws IOException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Empty input reading List"); - SpanBytesCodec.JSON.decodeList(new byte[0]); + SpanBytesCodec.JSON_V2.decodeList(new byte[0]); } /** @@ -132,28 +120,18 @@ public class SpanJsonSpanAdaptersTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Malformed reading List from "); - SpanBytesCodec.JSON.decodeList(new byte[] {'h', 'e', 'l', 'l', 'o'}); + SpanBytesCodec.JSON_V2.decodeList(new byte[] {'h', 'e', 'l', 'l', 'o'}); } @Test public void spansRoundTrip() throws IOException { List tenClientSpans = Collections.nCopies(10, span); - byte[] message = SpanBytesEncoder.JSON.encodeList(tenClientSpans); + byte[] message = SpanBytesEncoder.JSON_V2.encodeList(tenClientSpans); - assertThat(SpanBytesCodec.JSON.decodeList(message)) + assertThat(SpanBytesCodec.JSON_V2.decodeList(message)) .isEqualTo(tenClientSpans); } - @Test public void writesTraceIdHighIntoTraceIdField() { - Span with128BitTraceId = Span.newBuilder() - .traceId("48485a3953bb61246b221d5bc9e6496c") - .localEndpoint(toEndpoint(frontend)) - .id("1").name("").build(); - - assertThat(new String(SpanBytesEncoder.JSON.encode(with128BitTraceId), UTF_8)) - .startsWith("{\"traceId\":\"48485a3953bb61246b221d5bc9e6496c\""); - } - @Test public void readsTraceIdHighFromTraceIdField() { byte[] with128BitTraceId = ("{\n" + " \"traceId\": \"48485a3953bb61246b221d5bc9e6496c\",\n" @@ -166,8 +144,8 @@ public class SpanJsonSpanAdaptersTest { + " \"id\": \"6b221d5bc9e6496c\"\n" + "}").getBytes(UTF_8); - assertThat(SpanBytesCodec.JSON.decode(with128BitTraceId)) - .isEqualTo(SpanBytesCodec.JSON.decode(withLower64bitsTraceId).toBuilder() + assertThat(SpanBytesCodec.JSON_V2.decode(with128BitTraceId)) + .isEqualTo(SpanBytesCodec.JSON_V2.decode(withLower64bitsTraceId).toBuilder() .traceId("48485a3953bb61246b221d5bc9e6496c").build()); } @@ -187,7 +165,7 @@ public class SpanJsonSpanAdaptersTest { + " \"shared\": null\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void ignoresNull_endpoint_topLevelFields() { @@ -203,7 +181,8 @@ public class SpanJsonSpanAdaptersTest { + " }\n" + "}"; - assertThat(V2SpanConverter.toEndpoint(SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)).localEndpoint())) + assertThat( + V2SpanConverter.toEndpoint(SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)).localEndpoint())) .isEqualTo(Endpoint.create("", 127 << 24 | 1)); } @@ -222,7 +201,7 @@ public class SpanJsonSpanAdaptersTest { + " \"port\": null\n" + " }\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnIncomplete_annotation() { @@ -238,7 +217,7 @@ public class SpanJsonSpanAdaptersTest { + " ]\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_traceId() { @@ -251,7 +230,7 @@ public class SpanJsonSpanAdaptersTest { + " \"id\": \"6b221d5bc9e6496c\"\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_id() { @@ -264,7 +243,7 @@ public class SpanJsonSpanAdaptersTest { + " \"id\": null\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_tagValue() { @@ -280,7 +259,7 @@ public class SpanJsonSpanAdaptersTest { + " }\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_annotationValue() { @@ -296,7 +275,7 @@ public class SpanJsonSpanAdaptersTest { + " ]\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_annotationTimestamp() { @@ -312,7 +291,7 @@ public class SpanJsonSpanAdaptersTest { + " ]\n" + "}"; - SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)); + SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)); } @Test public void readSpan_localEndpoint_noServiceName() { @@ -325,7 +304,7 @@ public class SpanJsonSpanAdaptersTest { + " }\n" + "}"; - assertThat(SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)).localServiceName()) + assertThat(SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)).localServiceName()) .isNull(); } @@ -339,7 +318,7 @@ public class SpanJsonSpanAdaptersTest { + " }\n" + "}"; - assertThat(SpanBytesCodec.JSON.decode(json.getBytes(UTF_8)).remoteServiceName()) + assertThat(SpanBytesCodec.JSON_V2.decode(json.getBytes(UTF_8)).remoteServiceName()) .isNull(); } @@ -347,16 +326,7 @@ public class SpanJsonSpanAdaptersTest { span = span.toBuilder() .remoteEndpoint(toEndpoint(backend.toBuilder().serviceName("").build())).build(); - assertThat(SpanBytesCodec.JSON.decode(SpanBytesEncoder.JSON.encode(span))) + assertThat(SpanBytesCodec.JSON_V2.decode(SpanBytesEncoder.JSON_V2.encode(span))) .isEqualTo(span); } - - @Test public void doesntWriteEmptyServiceName() throws IOException { - span = span.toBuilder() - .localEndpoint(toEndpoint(frontend.toBuilder().serviceName("").build())) - .remoteEndpoint(null).build(); - - assertThat(new String(SpanBytesEncoder.JSON.encode(span), UTF_8)) - .contains("{\"ipv4\":\"127.0.0.1\"}"); - } } diff --git a/zipkin/src/test/java/zipkin/internal/v2/internal/V1SpanWriterTest.java b/zipkin/src/test/java/zipkin/internal/v2/internal/V1SpanWriterTest.java new file mode 100644 index 00000000000..1d5b4742671 --- /dev/null +++ b/zipkin/src/test/java/zipkin/internal/v2/internal/V1SpanWriterTest.java @@ -0,0 +1,226 @@ +/** + * Copyright 2015-2017 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.internal.v2.internal; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import zipkin.TraceKeys; +import zipkin.internal.v2.Endpoint; +import zipkin.internal.v2.Span; + +import static org.assertj.core.api.Assertions.assertThat; + +public class V1SpanWriterTest { + V1SpanWriter writer = new V1SpanWriter(); + Buffer buf = new Buffer(2048); // bigger than needed to test sizeOf + + Endpoint frontend = Endpoint.newBuilder() + .serviceName("frontend") + .ip("127.0.0.1") + .build(); + Endpoint backend = Endpoint.newBuilder() + .serviceName("backend") + .ip("192.168.99.101") + .port(9000) + .build(); + + // TODO: check for core annotation! + Span span = Span.newBuilder() + .traceId("7180c278b62e8f6a216a2aea45d08fc9") + .parentId("6b221d5bc9e6496c") + .id("5b4185666d50f68b") + .name("get") + .kind(Span.Kind.CLIENT) + .localEndpoint(frontend) + .remoteEndpoint(backend) + .timestamp(1472470996199000L) + .duration(207000L) + .addAnnotation(1472470996238000L, "foo") + .putTag(TraceKeys.HTTP_PATH, "/api") + .putTag("clnt/finagle.version", "6.45.0") + .build(); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test public void sizeInBytes() throws IOException { + writer.write(span, buf); + + assertThat(writer.sizeInBytes(span)) + .isEqualTo(buf.pos); + } + + @Test public void writesCoreAnnotations_client() throws IOException { + writer.write(span, buf); + + writesCoreAnnotations("cs", "cr"); + } + + @Test public void writesCoreAnnotations_server() throws IOException { + writer.write(span.toBuilder().kind(Span.Kind.SERVER).build(), buf); + + writesCoreAnnotations("sr", "ss"); + } + + @Test public void writesCoreAnnotations_producer() throws IOException { + writer.write(span.toBuilder().kind(Span.Kind.PRODUCER).build(), buf); + + writesCoreAnnotations("ms", "ws"); + } + + @Test public void writesCoreAnnotations_consumer() throws IOException { + writer.write(span.toBuilder().kind(Span.Kind.CONSUMER).build(), buf); + + writesCoreAnnotations("wr", "mr"); + } + + void writesCoreAnnotations(String begin, String end) throws UnsupportedEncodingException { + String json = new String(buf.toByteArray(), "UTF-8"); + + assertThat(json).contains( + "{\"timestamp\":" + span.timestamp() + ",\"value\":\"" + begin + "\""); + assertThat(json).contains( + "{\"timestamp\":" + (span.timestamp() + span.duration()) + ",\"value\":\"" + end + "\""); + } + + @Test public void writesCoreSendAnnotations_client() throws IOException { + writer.write(span.toBuilder().duration(null).build(), buf); + + writesCoreSendAnnotations("cs"); + } + + @Test public void writesCoreSendAnnotations_server() throws IOException { + writer.write(span.toBuilder().duration(null).kind(Span.Kind.SERVER).build(), buf); + + writesCoreSendAnnotations("sr"); + } + + @Test public void writesCoreSendAnnotations_producer() throws IOException { + writer.write(span.toBuilder().duration(null).kind(Span.Kind.PRODUCER).build(), buf); + + writesCoreSendAnnotations("ms"); + } + + @Test public void writesCoreSendAnnotations_consumer() throws IOException { + writer.write(span.toBuilder().duration(null).kind(Span.Kind.CONSUMER).build(), buf); + + writesCoreSendAnnotations("mr"); + } + + void writesCoreSendAnnotations(String begin) throws UnsupportedEncodingException { + String json = new String(buf.toByteArray(), "UTF-8"); + + assertThat(json).contains( + "{\"timestamp\":" + span.timestamp() + ",\"value\":\"" + begin + "\""); + } + + @Test public void writesAddressBinaryAnnotation_client() throws IOException { + writer.write(span.toBuilder().build(), buf); + + writesAddressBinaryAnnotation("sa"); + } + + @Test public void writesAddressBinaryAnnotation_server() throws IOException { + writer.write(span.toBuilder().kind(Span.Kind.SERVER).build(), buf); + + writesAddressBinaryAnnotation("ca"); + } + + @Test public void writesAddressBinaryAnnotation_producer() throws IOException { + writer.write(span.toBuilder().kind(Span.Kind.PRODUCER).build(), buf); + + writesAddressBinaryAnnotation("ma"); + } + + @Test public void writesAddressBinaryAnnotation_consumer() throws IOException { + writer.write(span.toBuilder().kind(Span.Kind.CONSUMER).build(), buf); + + writesAddressBinaryAnnotation("ma"); + } + + void writesAddressBinaryAnnotation(String address) throws UnsupportedEncodingException { + String json = new String(buf.toByteArray(), "UTF-8"); + + assertThat(json) + .contains("{\"key\":\"" + address + "\",\"value\":true,\"endpoint\":"); + } + + @Test public void writes128BitTraceId() throws UnsupportedEncodingException { + String traceId = "48485a3953bb61246b221d5bc9e6496c"; + span = Span.newBuilder().traceId(traceId).id("1").build(); + + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .startsWith("{\"traceId\":\"" + traceId + "\""); + } + + @Test public void annotationsHaveEndpoints() throws IOException { + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")).contains( + "\"value\":\"foo\",\"endpoint\":{\"serviceName\":\"frontend\",\"ipv4\":\"127.0.0.1\"}"); + } + + @Test public void writesTimestampAndDuration() throws IOException { + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .contains("\"timestamp\":" + span.timestamp() + ",\"duration\":" + span.duration()); + } + + @Test public void skipsTimestampAndDuration_shared() throws IOException { + writer.write(span.toBuilder().kind(Span.Kind.SERVER).shared(true).build(), buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .doesNotContain("\"timestamp\":" + span.timestamp() + ",\"duration\":" + span.duration()); + } + + @Test public void writesEmptySpanName() throws IOException { + span = Span.newBuilder() + .traceId("7180c278b62e8f6a216a2aea45d08fc9") + .parentId("6b221d5bc9e6496c") + .id("5b4185666d50f68b") + .build(); + + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .contains("\"name\":\"\""); + } + + @Test public void writesEmptyServiceName() throws IOException { + span = span.toBuilder() + .localEndpoint(Endpoint.newBuilder().ip("127.0.0.1").build()) + .build(); + + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .contains("\"value\":\"foo\",\"endpoint\":{\"serviceName\":\"\",\"ipv4\":\"127.0.0.1\"}"); + } + + @Test public void tagsAreBinaryAnnotations() throws IOException { + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .contains("\"binaryAnnotations\":[" + + + "{\"key\":\"clnt/finagle.version\",\"value\":\"6.45.0\",\"endpoint\":{\"serviceName\":\"frontend\",\"ipv4\":\"127.0.0.1\"}}," + + + "{\"key\":\"http.path\",\"value\":\"/api\",\"endpoint\":{\"serviceName\":\"frontend\",\"ipv4\":\"127.0.0.1\"}}"); + } +} diff --git a/zipkin/src/test/java/zipkin/internal/v2/internal/V2SpanWriterTest.java b/zipkin/src/test/java/zipkin/internal/v2/internal/V2SpanWriterTest.java new file mode 100644 index 00000000000..7c809bdf6b6 --- /dev/null +++ b/zipkin/src/test/java/zipkin/internal/v2/internal/V2SpanWriterTest.java @@ -0,0 +1,111 @@ +/** + * Copyright 2015-2017 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.internal.v2.internal; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import zipkin.TraceKeys; +import zipkin.internal.v2.Endpoint; +import zipkin.internal.v2.Span; + +import static org.assertj.core.api.Assertions.assertThat; + +public class V2SpanWriterTest { + V2SpanWriter writer = new V2SpanWriter(); + Buffer buf = new Buffer(2048); // bigger than needed to test sizeOf + + Endpoint frontend = Endpoint.newBuilder() + .serviceName("frontend") + .ip("127.0.0.1") + .build(); + Endpoint backend = Endpoint.newBuilder() + .serviceName("backend") + .ip("192.168.99.101") + .port(9000) + .build(); + + Span span = Span.newBuilder() + .traceId("7180c278b62e8f6a216a2aea45d08fc9") + .parentId("6b221d5bc9e6496c") + .id("5b4185666d50f68b") + .name("get") + .kind(Span.Kind.CLIENT) + .localEndpoint(frontend) + .remoteEndpoint(backend) + .timestamp(1472470996199000L) + .duration(207000L) + .addAnnotation(1472470996238000L, "foo") + .putTag(TraceKeys.HTTP_PATH, "/api") + .putTag("clnt/finagle.version", "6.45.0") + .build(); + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test public void sizeInBytes() throws IOException { + writer.write(span, buf); + assertThat(writer.sizeInBytes(span)) + .isEqualTo(buf.pos); + } + + @Test public void writes128BitTraceId() throws UnsupportedEncodingException { + String traceId = "48485a3953bb61246b221d5bc9e6496c"; + span = Span.newBuilder().traceId(traceId).id("1").build(); + + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .startsWith("{\"traceId\":\"" + traceId + "\""); + } + + @Test public void writesAnnotationWithoutEndpoint() throws IOException { + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .contains("{\"timestamp\":1472470996238000,\"value\":\"foo\"}"); + } + + @Test public void omitsEmptySpanName() throws IOException { + span = Span.newBuilder() + .traceId("7180c278b62e8f6a216a2aea45d08fc9") + .parentId("6b221d5bc9e6496c") + .id("5b4185666d50f68b") + .build(); + + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .doesNotContain("name"); + } + + @Test public void omitsEmptyServiceName() throws IOException { + span = span.toBuilder() + .localEndpoint(Endpoint.newBuilder().ip("127.0.0.1").build()) + .build(); + + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .contains("\"localEndpoint\":{\"ipv4\":\"127.0.0.1\"}"); + } + + @Test public void tagsAreAMap() throws IOException { + writer.write(span, buf); + + assertThat(new String(buf.toByteArray(), "UTF-8")) + .contains("\"tags\":{\"clnt/finagle.version\":\"6.45.0\",\"http.path\":\"/api\"}"); + } +}