From 8a33b2a6d3ce87cf3a43785b991bc6456965735b Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sat, 2 Sep 2017 12:48:04 +0800 Subject: [PATCH] Changes v2 IDs and IPs to validated Strings (#1721) --- .../zipkin/benchmarks/CodecBenchmarks.java | 30 +- .../benchmarks/Span2ConverterBenchmarks.java | 8 +- .../zipkin/benchmarks/SpanBenchmarks.java | 30 +- .../src/main/resources/span-client.json | 2 +- benchmarks/src/main/resources/span2.json | 8 +- .../collector/kafka/KafkaCollectorTest.java | 9 +- .../collector/kafka10/KafkaCollectorTest.java | 9 +- .../java/zipkin/junit/ZipkinDispatcher.java | 32 +- .../test/java/zipkin/junit/ITHttpStorage.java | 5 - .../java/zipkin/junit/ZipkinRuleTest.java | 9 +- .../zipkin/junit/v2/HttpV2SpanConsumer.java | 4 +- .../java/zipkin/junit/v2/HttpV2SpanStore.java | 12 +- .../zipkin/server/EnableZipkinServer.java | 1 - .../java/zipkin/server/ZipkinQueryApiV2.java | 44 +- .../server/ZipkinServerIntegrationTest.java | 7 +- .../cassandra3/CassandraSpanStore.java | 1 - .../http/ElasticsearchHttpSpanConsumer.java | 14 +- .../http/ElasticsearchHttpSpanStore.java | 40 +- .../http/ElasticsearchHttpStorage.java | 2 + .../elasticsearch/http/HttpBulkIndexer.java | 1 - .../elasticsearch/http/JsonAdapters.java | 46 +- .../http/LegacyJsonAdapters.java | 72 ++- .../ElasticsearchHttpSpanConsumerTest.java | 60 ++- .../http/ElasticsearchHttpSpanStoreTest.java | 19 + .../elasticsearch/http/InternalForTests.java | 1 - .../elasticsearch/http/JsonAdaptersTest.java | 21 +- .../mysql/DependencyLinkV2SpanIterator.java | 20 +- .../DependencyLinkV2SpanIteratorTest.java | 24 +- .../main/java/zipkin/collector/Collector.java | 4 +- .../zipkin/collector/CollectorMetrics.java | 1 - .../zipkin/internal/CorrectForClockSkew.java | 8 +- .../zipkin/internal/DependencyLinker.java | 4 +- .../main/java/zipkin/internal/JsonCodec.java | 14 +- .../src/main/java/zipkin/internal/Node.java | 19 +- .../java/zipkin/internal/V2Collector.java | 12 +- .../zipkin/internal/V2JsonSpanDecoder.java | 4 +- .../java/zipkin/internal/V2SpanConverter.java | 161 ++++--- .../zipkin/internal/V2SpanStoreAdapter.java | 10 +- .../java/zipkin/internal/v2/Annotation.java | 57 +++ .../java/zipkin/internal/v2/Endpoint.java | 438 ++++++++++++++++++ .../main/java/zipkin/internal/v2/Span.java | 217 +++++---- .../codec/{Decoder.java => BytesDecoder.java} | 25 +- .../codec/{Encoder.java => BytesEncoder.java} | 25 +- .../internal/v2/codec/MessageEncoder.java | 54 --- .../internal/v2/codec/Span2JsonAdapters.java | 133 +++--- .../internal/v2/storage/InMemoryStorage.java | 139 +++--- .../internal/v2/storage/QueryRequest.java | 10 +- .../internal/v2/storage/SpanConsumer.java | 1 - .../zipkin/internal/v2/storage/SpanStore.java | 9 +- .../zipkin/storage/AsyncSpanConsumer.java | 1 - .../java/zipkin/collector/CollectorTest.java | 7 +- .../zipkin/internal/DependencyLinkerTest.java | 16 +- .../internal/DetectingSpanDecoderTest.java | 13 +- .../test/java/zipkin/internal/NodeTest.java | 22 +- .../internal/V2JsonSpanDecoderTest.java | 10 +- .../zipkin/internal/V2SpanConverterTest.java | 141 +++--- .../internal/V2SpanStoreAdapterTest.java | 47 +- .../zipkin/internal/v2/AnnotationTest.java | 36 ++ .../java/zipkin/internal/v2/EndpointTest.java | 195 ++++++++ .../java/zipkin/internal/v2/SpanTest.java | 55 +-- .../v2/codec/MessageEncodingTest.java | 43 -- .../v2/codec/SpanJsonAdaptersTest.java | 179 ++++--- .../v2/storage/InMemoryStorageTest.java | 29 +- .../internal/v2/storage/QueryRequestTest.java | 20 +- 64 files changed, 1696 insertions(+), 994 deletions(-) create mode 100644 zipkin/src/main/java/zipkin/internal/v2/Annotation.java create mode 100644 zipkin/src/main/java/zipkin/internal/v2/Endpoint.java rename zipkin/src/main/java/zipkin/internal/v2/codec/{Decoder.java => BytesDecoder.java} (60%) rename zipkin/src/main/java/zipkin/internal/v2/codec/{Encoder.java => BytesEncoder.java} (60%) delete mode 100644 zipkin/src/main/java/zipkin/internal/v2/codec/MessageEncoder.java create mode 100644 zipkin/src/test/java/zipkin/internal/v2/AnnotationTest.java create mode 100644 zipkin/src/test/java/zipkin/internal/v2/EndpointTest.java delete mode 100644 zipkin/src/test/java/zipkin/internal/v2/codec/MessageEncodingTest.java diff --git a/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java b/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java index 50d8a1e6fdb..197090bac6e 100644 --- a/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java +++ b/benchmarks/src/main/java/zipkin/benchmarks/CodecBenchmarks.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.apache.thrift.TDeserializer; import org.apache.thrift.TException; import org.apache.thrift.TSerializer; @@ -42,9 +41,8 @@ import zipkin.Codec; import zipkin.Endpoint; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.MessageEncoder; -import zipkin.internal.v2.codec.Decoder; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesDecoder; +import zipkin.internal.v2.codec.BytesEncoder; /** * This compares the speed of the bundled java codec with the approach used in the scala @@ -158,19 +156,29 @@ public byte[] writeClientSpan_thrift_libthrift() throws TException { return serialize(clientSpanLibThrift); } - static final Span span2 = Decoder.JSON.decodeList(read("/span2.json")).get(0); - static final byte[] tenClientSpan2sJson = MessageEncoder.JSON_BYTES.encode( - Collections.nCopies(10, span2).stream().map(Encoder.JSON::encode).collect(Collectors.toList()) - ); + static final byte[] span2Json = read("/span2.json"); + static final Span span2 = BytesDecoder.JSON.decode(span2Json); + static final List tenSpan2s = Collections.nCopies(10, span2); + static final byte[] tenSpan2sJson = BytesEncoder.JSON.encodeList(tenSpan2s); + + @Benchmark + public Span readClientSpan_json_span2() { + return BytesDecoder.JSON.decode(span2Json); + } @Benchmark public List readTenClientSpans_json_span2() { - return Decoder.JSON.decodeList(tenClientSpan2sJson); + return BytesDecoder.JSON.decodeList(tenSpan2sJson); } @Benchmark public byte[] writeClientSpan_json_span2() { - return Encoder.JSON.encode(span2); + return BytesEncoder.JSON.encode(span2); + } + + @Benchmark + public byte[] writeTenClientSpans_json_span2() { + return BytesEncoder.JSON.encodeList(tenSpan2s); } static final byte[] rpcSpanJson = read("/span-rpc.json"); @@ -246,7 +254,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.readTenClientSpans_json_span2") + .include(".*" + CodecBenchmarks.class.getSimpleName() + ".*ClientSpan.*") .build(); new Runner(opt).run(); diff --git a/benchmarks/src/main/java/zipkin/benchmarks/Span2ConverterBenchmarks.java b/benchmarks/src/main/java/zipkin/benchmarks/Span2ConverterBenchmarks.java index 08894d3ea77..3601e3e1222 100644 --- a/benchmarks/src/main/java/zipkin/benchmarks/Span2ConverterBenchmarks.java +++ b/benchmarks/src/main/java/zipkin/benchmarks/Span2ConverterBenchmarks.java @@ -38,6 +38,8 @@ import zipkin.internal.V2SpanConverter; import zipkin.internal.Util; +import static zipkin.internal.V2SpanConverter.convert; + @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @Fork(3) @@ -88,15 +90,15 @@ public class Span2ConverterBenchmarks { .addBinaryAnnotation(BinaryAnnotation.address(Constants.CLIENT_ADDR, frontend)) .build(); - Span server2 = Span.builder() + Span server2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("get") .kind(Span.Kind.SERVER) .shared(true) - .localEndpoint(backend) - .remoteEndpoint(frontend) + .localEndpoint(convert(backend)) + .remoteEndpoint(convert(frontend)) .timestamp(1472470996250000L) .duration(100000L) .putTag(TraceKeys.HTTP_PATH, "/backend") diff --git a/benchmarks/src/main/java/zipkin/benchmarks/SpanBenchmarks.java b/benchmarks/src/main/java/zipkin/benchmarks/SpanBenchmarks.java index 7cb5fd54d5b..6d1e5da24e9 100644 --- a/benchmarks/src/main/java/zipkin/benchmarks/SpanBenchmarks.java +++ b/benchmarks/src/main/java/zipkin/benchmarks/SpanBenchmarks.java @@ -33,8 +33,8 @@ import zipkin.Constants; import zipkin.Endpoint; import zipkin.TraceKeys; -import zipkin.internal.v2.Span; import zipkin.internal.Util; +import zipkin.internal.v2.Span; @Measurement(iterations = 5, time = 1) @Warmup(iterations = 10, time = 1) @@ -69,15 +69,25 @@ public zipkin.Span buildLocalSpan() { .build(); } - static final long traceId = Util.lowerHexToUnsignedLong("86154a4ba6e91385"); - static final long spanId = Util.lowerHexToUnsignedLong("4d1e00c0db9010db"); + static final String traceIdHex = "86154a4ba6e91385"; + static final String spanIdHex = "4d1e00c0db9010db"; + static final long traceId = Util.lowerHexToUnsignedLong(traceIdHex); + static final long spanId = Util.lowerHexToUnsignedLong(spanIdHex); static final Endpoint frontend = Endpoint.create("frontend", 127 << 24 | 1); static final Endpoint backend = Endpoint.builder() .serviceName("backend") .ipv4(192 << 24 | 168 << 16 | 99 << 8 | 101) .port(9000) .build(); - + static final zipkin.internal.v2.Endpoint frontend2 = zipkin.internal.v2.Endpoint.newBuilder() + .serviceName("frontend") + .ip("127.0.0.1") + .build(); + static final zipkin.internal.v2.Endpoint backend2 = zipkin.internal.v2.Endpoint.newBuilder() + .serviceName("backend") + .ip("192.168.99.101") + .port(9000) + .build(); @Benchmark public zipkin.Span buildClientOnlySpan() { return buildClientOnlySpan(zipkin.Span.builder()); @@ -108,18 +118,18 @@ public zipkin.Span buildClientOnlySpan_clear() { @Benchmark public Span buildClientOnlySpan2() { - return buildClientOnlySpan2(Span.builder()); + return buildClientOnlySpan2(Span.newBuilder()); } static Span buildClientOnlySpan2(Span.Builder builder) { return builder - .traceId(traceId) - .parentId(traceId) - .id(spanId) + .traceId(traceIdHex) + .parentId(traceIdHex) + .id(spanIdHex) .name("get") .kind(Span.Kind.CLIENT) - .localEndpoint(frontend) - .remoteEndpoint(backend) + .localEndpoint(frontend2) + .remoteEndpoint(backend2) .timestamp(1472470996199000L) .duration(207000L) .addAnnotation(1472470996238000L, Constants.WIRE_SEND) diff --git a/benchmarks/src/main/resources/span-client.json b/benchmarks/src/main/resources/span-client.json index 466eb2a29ae..da8b4b65fb4 100644 --- a/benchmarks/src/main/resources/span-client.json +++ b/benchmarks/src/main/resources/span-client.json @@ -1,5 +1,5 @@ { - "traceId": "86154a4ba6e91385", + "traceId": "4d1e00c0db9010db86154a4ba6e91385", "name": "get", "id": "4d1e00c0db9010db", "parentId": "86154a4ba6e91385", diff --git a/benchmarks/src/main/resources/span2.json b/benchmarks/src/main/resources/span2.json index 7dbc990d325..45ea81918d4 100644 --- a/benchmarks/src/main/resources/span2.json +++ b/benchmarks/src/main/resources/span2.json @@ -1,5 +1,5 @@ -[{ - "traceId": "86154a4ba6e91385", +{ + "traceId": "4d1e00c0db9010db86154a4ba6e91385", "parentId": "86154a4ba6e91385", "id": "4d1e00c0db9010db", "kind": "CLIENT", @@ -8,7 +8,7 @@ "duration": 207000, "localEndpoint": { "serviceName": "frontend", - "ipv4": "127.0.0.1" + "ipv6": "7::0.128.128.127" }, "remoteEndpoint": { "serviceName": "backend", @@ -29,4 +29,4 @@ "http.path": "/api", "clnt/finagle.version": "6.45.0" } -}] +} 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 6bee5115fd4..b252f77c993 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,8 +32,7 @@ import zipkin.collector.kafka.KafkaCollector.Builder; import zipkin.internal.ApplyTimestampAndDuration; import zipkin.internal.V2SpanConverter; -import zipkin.internal.v2.codec.MessageEncoder; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import zipkin.storage.AsyncSpanConsumer; import zipkin.storage.AsyncSpanStore; import zipkin.storage.SpanStore; @@ -147,9 +146,9 @@ public void messageWithMultipleSpans_json2() throws Exception { ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[1]) ); - byte[] message = MessageEncoder.JSON_BYTES.encode(asList( - Encoder.JSON.encode(V2SpanConverter.fromSpan(spans.get(0)).get(0)), - Encoder.JSON.encode(V2SpanConverter.fromSpan(spans.get(1)).get(0)) + byte[] message = BytesEncoder.JSON.encodeList(asList( + V2SpanConverter.fromSpan(spans.get(0)).get(0), + V2SpanConverter.fromSpan(spans.get(1)).get(0) )); producer.send(new KeyedMessage<>(builder.topic, message)); 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 d5e8333a871..7486bf6d330 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,8 +39,7 @@ import zipkin.collector.kafka10.KafkaCollector.Builder; import zipkin.internal.ApplyTimestampAndDuration; import zipkin.internal.V2SpanConverter; -import zipkin.internal.v2.codec.MessageEncoder; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import zipkin.storage.AsyncSpanConsumer; import zipkin.storage.AsyncSpanStore; import zipkin.storage.SpanStore; @@ -201,9 +200,9 @@ public void messageWithMultipleSpans_json2() throws Exception { ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[1]) ); - byte[] message = MessageEncoder.JSON_BYTES.encode(asList( - Encoder.JSON.encode(V2SpanConverter.fromSpan(spans.get(0)).get(0)), - Encoder.JSON.encode(V2SpanConverter.fromSpan(spans.get(1)).get(0)) + byte[] message = BytesEncoder.JSON.encodeList(asList( + V2SpanConverter.fromSpan(spans.get(0)).get(0), + V2SpanConverter.fromSpan(spans.get(1)).get(0) )); produceSpans(message, builder.topic); diff --git a/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java b/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java index d4cb7d6fbfb..e57607f31b3 100644 --- a/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java +++ b/zipkin-junit/src/main/java/zipkin/junit/ZipkinDispatcher.java @@ -13,7 +13,6 @@ */ package zipkin.junit; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; import javax.annotation.Nullable; @@ -32,13 +31,14 @@ import zipkin.collector.CollectorMetrics; import zipkin.internal.V2JsonSpanDecoder; import zipkin.internal.V2StorageComponent; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import zipkin.internal.v2.internal.Platform; import zipkin.storage.Callback; import zipkin.storage.QueryRequest; import zipkin.storage.SpanStore; import static zipkin.internal.Util.lowerHexToUnsignedLong; +import static zipkin.internal.v2.Span.normalizeTraceId; final class ZipkinDispatcher extends Dispatcher { static final long DEFAULT_LOOKBACK = 86400000L; // 1 day in millis @@ -131,39 +131,17 @@ MockResponse queryV2(HttpUrl url) throws IOException { return jsonResponse(Codec.JSON.writeDependencyLinks(result)); } else if (url.encodedPath().equals("/api/v2/traces")) { List> traces = store2.getTraces(toQueryRequest2(url)).execute(); - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - bout.write('['); - for (int i = 0, length = traces.size(); i < length; ) { - List trace = traces.get(i); - writeTrace(bout, trace); - if (++i < length) bout.write(','); - } - bout.write(']'); - return jsonResponse(bout.toByteArray()); + return jsonResponse(BytesEncoder.JSON.encodeNestedList(traces)); } else if (url.encodedPath().startsWith("/api/v2/trace/")) { String traceIdHex = url.encodedPath().replace("/api/v2/trace/", ""); - long traceIdHigh = traceIdHex.length() == 32 ? lowerHexToUnsignedLong(traceIdHex, 0) : 0L; - long traceIdLow = lowerHexToUnsignedLong(traceIdHex); - List trace = store2.getTrace(traceIdHigh, traceIdLow).execute(); + List trace = store2.getTrace(normalizeTraceId(traceIdHex)).execute(); if (!trace.isEmpty()) { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - writeTrace(bout, trace); - return jsonResponse(bout.toByteArray()); + return jsonResponse(BytesEncoder.JSON.encodeList(trace)); } } return new MockResponse().setResponseCode(404); } - static void writeTrace(ByteArrayOutputStream bout, List trace) - throws IOException { - bout.write('['); - for (int i = 0, length = trace.size(); i < length; ) { - bout.write(Encoder.JSON.encode(trace.get(i))); - if (++i < length) bout.write(','); - } - bout.write(']'); - } - MockResponse acceptSpans(RecordedRequest request, SpanDecoder decoder) { metrics.incrementMessages(); byte[] body = request.getBody().readByteArray(); diff --git a/zipkin-junit/src/test/java/zipkin/junit/ITHttpStorage.java b/zipkin-junit/src/test/java/zipkin/junit/ITHttpStorage.java index eeb0c354332..db7f541b4ad 100644 --- a/zipkin-junit/src/test/java/zipkin/junit/ITHttpStorage.java +++ b/zipkin-junit/src/test/java/zipkin/junit/ITHttpStorage.java @@ -15,13 +15,8 @@ import java.io.IOException; import org.junit.Rule; -import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; -import zipkin.Span; -import zipkin.TestObjects; - -import static org.assertj.core.api.Assertions.assertThat; @RunWith(Enclosed.class) public class ITHttpStorage { diff --git a/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java b/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java index 0483037e66a..a2d7e41af01 100644 --- a/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java +++ b/zipkin-junit/src/test/java/zipkin/junit/ZipkinRuleTest.java @@ -32,8 +32,7 @@ import zipkin.Span; import zipkin.internal.ApplyTimestampAndDuration; import zipkin.internal.V2SpanConverter; -import zipkin.internal.v2.codec.MessageEncoder; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -69,9 +68,9 @@ public void getTraces_storedViaPostVersion2() throws IOException { ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[1]) ); - byte[] message = MessageEncoder.JSON_BYTES.encode(asList( - Encoder.JSON.encode(V2SpanConverter.fromSpan(spans.get(0)).get(0)), - Encoder.JSON.encode(V2SpanConverter.fromSpan(spans.get(1)).get(0)) + byte[] message = BytesEncoder.JSON.encodeList(asList( + V2SpanConverter.fromSpan(spans.get(0)).get(0), + V2SpanConverter.fromSpan(spans.get(1)).get(0) )); // write the span to the zipkin using http api v2 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 814fd6e2ab7..e264ba5bb6a 100644 --- a/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanConsumer.java +++ b/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanConsumer.java @@ -21,7 +21,7 @@ import okhttp3.RequestBody; import okio.Buffer; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import zipkin.internal.v2.storage.SpanConsumer; /** Implements the span consumer interface by forwarding requests over http. */ @@ -36,7 +36,7 @@ final class HttpV2SpanConsumer implements SpanConsumer { Buffer json = new Buffer(); json.writeByte('['); for (int i = 0, length = spans.size(); i < length; ) { - json.write(Encoder.JSON.encode(spans.get(i))); + json.write(BytesEncoder.JSON.encode(spans.get(i))); if (++i < length) json.writeByte(','); } json.writeByte(']'); 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 c78e5a40095..4876fa6f771 100644 --- a/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanStore.java +++ b/zipkin-junit/src/test/java/zipkin/junit/v2/HttpV2SpanStore.java @@ -21,10 +21,9 @@ import okhttp3.Request; import zipkin.Codec; import zipkin.DependencyLink; -import zipkin.internal.Util; import zipkin.internal.v2.Call; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Decoder; +import zipkin.internal.v2.codec.BytesDecoder; import zipkin.internal.v2.storage.QueryRequest; import zipkin.internal.v2.storage.SpanStore; @@ -47,14 +46,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 -> Decoder.JSON.decodeNestedList(content.readByteArray())); + content -> BytesDecoder.JSON.decodeNestedList(content.readByteArray())); } - @Override public Call> getTrace(long traceIdHigh, long traceIdLow) { - String traceIdHex = Util.toLowerHex(traceIdHigh, traceIdLow); + @Override public Call> getTrace(String traceId) { return factory.newCall(new Request.Builder() - .url(factory.baseUrl.resolve("/api/v2/trace/" + traceIdHex)) - .build(), content -> Decoder.JSON.decodeList(content.readByteArray())) + .url(factory.baseUrl.resolve("/api/v2/trace/" + Span.normalizeTraceId(traceId))) + .build(), content -> BytesDecoder.JSON.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/EnableZipkinServer.java b/zipkin-server/src/main/java/zipkin/server/EnableZipkinServer.java index 698d41133a7..7e9107e8c9c 100644 --- a/zipkin-server/src/main/java/zipkin/server/EnableZipkinServer.java +++ b/zipkin-server/src/main/java/zipkin/server/EnableZipkinServer.java @@ -19,7 +19,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; -import zipkin.autoconfigure.ui.ZipkinUiAutoConfiguration; import zipkin.server.brave.BraveConfiguration; @Target(ElementType.TYPE) diff --git a/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java b/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java index 1b4e974b169..bc1c10b8f88 100644 --- a/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java +++ b/zipkin-server/src/main/java/zipkin/server/ZipkinQueryApiV2.java @@ -14,10 +14,10 @@ package zipkin.server; import java.io.IOException; +import java.nio.charset.Charset; import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; -import okio.Buffer; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.CacheControl; @@ -37,18 +37,19 @@ import zipkin.internal.V2StorageComponent; import zipkin.internal.v2.Call; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import zipkin.internal.v2.storage.QueryRequest; import zipkin.storage.StorageComponent; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static zipkin.internal.Util.lowerHexToUnsignedLong; @RestController @RequestMapping("/api/v2") @CrossOrigin("${zipkin.query.allowed-origins:*}") @ConditionalOnProperty(name = "zipkin.query.enabled", matchIfMissing = true) public class ZipkinQueryApiV2 { + static final Charset UTF_8 = Charset.forName("UTF-8"); + final String storageType; final V2StorageComponent storage; // don't cache spanStore here as it can cause the app to crash! final long defaultLookback; @@ -127,40 +128,16 @@ public String getTraces( .limit(limit).build(); List> traces = storage.v2SpanStore().getTraces(queryRequest).execute(); - Buffer buffer = new Buffer(); - buffer.writeByte('['); - for (int i = 0, iLength = traces.size(); i < iLength; ) { - buffer.writeByte('['); - List trace = traces.get(i); - for (int j = 0, jLength = trace.size(); j < jLength; ) { - buffer.write(Encoder.JSON.encode(trace.get(j))); - if (++j < jLength) buffer.writeByte(','); - } - buffer.writeByte(']'); - if (++i < iLength) buffer.writeByte(','); - } - buffer.writeByte(']'); - return buffer.readUtf8(); + return new String(BytesEncoder.JSON.encodeNestedList(traces), UTF_8); } @RequestMapping(value = "/trace/{traceIdHex}", method = RequestMethod.GET, produces = APPLICATION_JSON_VALUE) public String getTrace(@PathVariable String traceIdHex, WebRequest request) throws IOException { if (storage == null) throw new Version2StorageNotConfigured(); - long traceIdHigh = traceIdHex.length() == 32 ? lowerHexToUnsignedLong(traceIdHex, 0) : 0L; - long traceIdLow = lowerHexToUnsignedLong(traceIdHex); - List trace = storage.v2SpanStore().getTrace(traceIdHigh, traceIdLow).execute(); - if (trace.isEmpty()) { - throw new TraceNotFoundException(traceIdHex, traceIdHigh, traceIdLow); - } - Buffer buffer = new Buffer(); - buffer.writeByte('['); - for (int i = 0, length = trace.size(); i < length; ) { - buffer.write(Encoder.JSON.encode(trace.get(i))); - if (++i < length) buffer.writeByte(','); - } - buffer.writeByte(']'); - return buffer.readUtf8(); + List trace = storage.v2SpanStore().getTrace(traceIdHex).execute(); + if (trace.isEmpty()) throw new TraceNotFoundException(traceIdHex); + return new String(BytesEncoder.JSON.encodeList(trace), UTF_8); } @ExceptionHandler(Version2StorageNotConfigured.class) @@ -181,9 +158,8 @@ public void notFound() { } static class TraceNotFoundException extends RuntimeException { - TraceNotFoundException(String traceIdHex, long traceIdHigh, long traceId) { - super(String.format("Cannot find trace for id=%s, parsed value=%s", traceIdHex, - traceIdHigh != 0 ? traceIdHigh + "," + traceId : traceId)); + TraceNotFoundException(String traceIdHex) { + super("Cannot find trace " + traceIdHex); } } diff --git a/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java b/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java index 24b1c356d09..7db05ec9e30 100644 --- a/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java +++ b/zipkin-server/src/test/java/zipkin/server/ZipkinServerIntegrationTest.java @@ -35,8 +35,7 @@ import zipkin.internal.ApplyTimestampAndDuration; import zipkin.internal.V2InMemoryStorage; import zipkin.internal.V2SpanConverter; -import zipkin.internal.v2.codec.Encoder; -import zipkin.internal.v2.codec.MessageEncoder; +import zipkin.internal.v2.codec.BytesEncoder; import static java.lang.String.format; import static java.util.Arrays.asList; @@ -86,8 +85,8 @@ public void writeSpans_noContentTypeIsJson() throws Exception { public void writeSpans_version2() throws Exception { Span span = ApplyTimestampAndDuration.apply(LOTS_OF_SPANS[0]); - byte[] message = MessageEncoder.JSON_BYTES.encode(asList( - Encoder.JSON.encode(V2SpanConverter.fromSpan(span).get(0)) + byte[] message = BytesEncoder.JSON.encodeList(asList( + V2SpanConverter.fromSpan(span).get(0) )); performAsync(post("/api/v2/spans").content(message)) diff --git a/zipkin-storage/cassandra3/src/main/java/zipkin/storage/cassandra3/CassandraSpanStore.java b/zipkin-storage/cassandra3/src/main/java/zipkin/storage/cassandra3/CassandraSpanStore.java index 727eb35d1f5..56d2605cc7e 100644 --- a/zipkin-storage/cassandra3/src/main/java/zipkin/storage/cassandra3/CassandraSpanStore.java +++ b/zipkin-storage/cassandra3/src/main/java/zipkin/storage/cassandra3/CassandraSpanStore.java @@ -47,7 +47,6 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; -import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import zipkin.Codec; 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 c4dc33f9c83..6526609111a 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 @@ -23,10 +23,10 @@ import javax.annotation.Nullable; import okio.Buffer; import okio.ByteString; -import zipkin.Annotation; +import zipkin.internal.v2.Annotation; import zipkin.internal.v2.Call; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import zipkin.internal.v2.storage.SpanConsumer; import zipkin.storage.elasticsearch.http.internal.client.HttpCall; @@ -60,7 +60,7 @@ void indexSpans(BulkSpanIndexer indexer, List spans) { // guessTimestamp is made for determining the span's authoritative timestamp. When choosing // the index bucket, any annotation is better than using current time. for (int i = 0, length = span.annotations().size(); i < length; i++) { - indexTimestamp = span.annotations().get(i).timestamp / 1000; + indexTimestamp = span.annotations().get(i).timestamp() / 1000; break; } if (indexTimestamp == 0L) indexTimestamp = System.currentTimeMillis(); @@ -114,8 +114,8 @@ static byte[] prefixWithTimestampMillisAndQuery(Span span, @Nullable Long timest writer.name("_q"); writer.beginArray(); for (Annotation a : span.annotations()) { - if (a.value.length() > 255) continue; - writer.value(a.value); + if (a.value().length() > 255) continue; + writer.value(a.value()); } for (Map.Entry tag : span.tags().entrySet()) { if (tag.getKey().length() + tag.getValue().length() + 1 > 255) continue; @@ -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 Encoder.JSON.encode(span); + return BytesEncoder.JSON.encode(span); } - byte[] document = Encoder.JSON.encode(span); + byte[] document = BytesEncoder.JSON.encode(span); if (query.rangeEquals(0L, ByteString.of(new byte[] {'{', '}'}))) { return document; } diff --git a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStore.java b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStore.java index 86eede3174b..75850044102 100644 --- a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStore.java +++ b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStore.java @@ -23,8 +23,6 @@ import java.util.Locale; import java.util.Map; import zipkin.DependencyLink; -import zipkin.internal.Pair; -import zipkin.internal.Util; import zipkin.internal.v2.Call; import zipkin.internal.v2.Span; import zipkin.internal.v2.storage.QueryRequest; @@ -92,14 +90,14 @@ final class ElasticsearchHttpSpanStore implements SpanStore { // be no significant difference in user experience since span start times are usually very // close to each other in human time. Aggregation traceIdTimestamp = Aggregation.terms("traceId", request.limit()) - .addSubAggregation(Aggregation.min("timestamp_millis")) - .orderBy("timestamp_millis", "desc"); + .addSubAggregation(Aggregation.min("timestamp_millis")) + .orderBy("timestamp_millis", "desc"); List indices = indexNameFormatter.formatTypeAndRange(SPAN, beginMillis, endMillis); if (indices.isEmpty()) return Call.emptyList(); SearchRequest esRequest = SearchRequest.create(indices) - .filters(filters).addAggregation(traceIdTimestamp); + .filters(filters).addAggregation(traceIdTimestamp); HttpCall> traceIdsCall = search.newCall(esRequest, BodyConverters.SORTED_KEYS); @@ -111,7 +109,7 @@ final class ElasticsearchHttpSpanStore implements SpanStore { // Due to tokenization of the trace ID, our matches are imprecise on Span.traceIdHigh for (Iterator> trace = traces.iterator(); trace.hasNext(); ) { List next = trace.next(); - if (next.get(0).traceIdHigh() != 0 && !request.test(next)) { + if (next.get(0).traceId().length() > 16 && !request.test(next)) { trace.remove(); } } @@ -126,12 +124,14 @@ final class ElasticsearchHttpSpanStore implements SpanStore { }); } - @Override public Call> getTrace(long traceIdHigh, long traceIdLow) { - String traceIdHex = Util.toLowerHex(strictTraceId ? traceIdHigh : 0L, traceIdLow); + @Override public Call> getTrace(String traceId) { + // make sure we have a 16 or 32 character trace ID + traceId = Span.normalizeTraceId(traceId); - SearchRequest request = SearchRequest.create(asList(allSpanIndices)) - .term("traceId", traceIdHex); + // Unless we are strict, truncate the trace ID to 64bit (encoded as 16 characters) + if (!strictTraceId && traceId.length() == 32) traceId = traceId.substring(16); + SearchRequest request = SearchRequest.create(asList(allSpanIndices)).term("traceId", traceId); return search.newCall(request, BodyConverters.SPANS); } @@ -147,9 +147,9 @@ final class ElasticsearchHttpSpanStore implements SpanStore { SearchRequest.Filters filters = new SearchRequest.Filters(); filters.addRange("timestamp_millis", beginMillis, endMillis); SearchRequest request = SearchRequest.create(indices) - .filters(filters) - .addAggregation(Aggregation.terms("localEndpoint.serviceName", Integer.MAX_VALUE)) - .addAggregation(Aggregation.terms("remoteEndpoint.serviceName", Integer.MAX_VALUE)); + .filters(filters) + .addAggregation(Aggregation.terms("localEndpoint.serviceName", Integer.MAX_VALUE)) + .addAggregation(Aggregation.terms("remoteEndpoint.serviceName", Integer.MAX_VALUE)); return search.newCall(request, BodyConverters.SORTED_KEYS); } @@ -164,12 +164,12 @@ final class ElasticsearchHttpSpanStore implements SpanStore { // A span name is only valid on a local endpoint, as a span name is defined locally SearchRequest.Filters filters = new SearchRequest.Filters() - .addRange("timestamp_millis", beginMillis, endMillis) - .addTerm("localEndpoint.serviceName", serviceName.toLowerCase(Locale.ROOT)); + .addRange("timestamp_millis", beginMillis, endMillis) + .addTerm("localEndpoint.serviceName", serviceName.toLowerCase(Locale.ROOT)); SearchRequest request = SearchRequest.create(indices) - .filters(filters) - .addAggregation(Aggregation.terms("name", Integer.MAX_VALUE)); + .filters(filters) + .addAggregation(Aggregation.terms("name", Integer.MAX_VALUE)); return search.newCall(request, BodyConverters.SORTED_KEYS); } @@ -188,9 +188,11 @@ final class ElasticsearchHttpSpanStore implements SpanStore { static List> groupByTraceId(Collection input, boolean strictTraceId) { if (input.isEmpty()) return Collections.emptyList(); - Map, List> groupedByTraceId = new LinkedHashMap<>(); + Map> groupedByTraceId = new LinkedHashMap<>(); for (Span span : input) { - Pair traceId = Pair.create(strictTraceId ? span.traceIdHigh() : 0L, span.traceId()); + String traceId = strictTraceId || span.traceId().length() == 16 + ? span.traceId() + : span.traceId().substring(16); if (!groupedByTraceId.containsKey(traceId)) { groupedByTraceId.put(traceId, new LinkedList<>()); } diff --git a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpStorage.java b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpStorage.java index ff7c2104cb7..d782c6494c7 100644 --- a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpStorage.java +++ b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpStorage.java @@ -73,6 +73,8 @@ public static Builder builder() { return result; } + abstract Builder toBuilder(); + @AutoValue.Builder public static abstract class Builder implements zipkin.storage.StorageComponent.Builder { abstract Builder client(OkHttpClient client); diff --git a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/HttpBulkIndexer.java b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/HttpBulkIndexer.java index b5cdd249ccb..53b5f16a042 100644 --- a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/HttpBulkIndexer.java +++ b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/HttpBulkIndexer.java @@ -23,7 +23,6 @@ import okhttp3.RequestBody; import okio.Buffer; import zipkin.internal.JsonCodec; -import zipkin.storage.Callback; import zipkin.storage.elasticsearch.http.internal.client.HttpCall; import static zipkin.storage.elasticsearch.http.ElasticsearchHttpStorage.APPLICATION_JSON; diff --git a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/JsonAdapters.java b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/JsonAdapters.java index c18aab32a62..d695e941081 100644 --- a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/JsonAdapters.java +++ b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/JsonAdapters.java @@ -13,15 +13,16 @@ */ package zipkin.storage.elasticsearch.http; +import com.google.gson.stream.MalformedJsonException; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import java.io.IOException; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import zipkin.Annotation; import zipkin.DependencyLink; -import zipkin.Endpoint; +import zipkin.internal.v2.Annotation; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; /** @@ -32,7 +33,7 @@ final class JsonAdapters { static final JsonAdapter SPAN_ADAPTER = new JsonAdapter() { @Override @Nonnull public Span fromJson(JsonReader reader) throws IOException { - Span.Builder result = Span.builder(); + Span.Builder result = Span.newBuilder(); reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); @@ -72,7 +73,7 @@ public Span fromJson(JsonReader reader) throws IOException { reader.beginArray(); while (reader.hasNext()) { Annotation a = ANNOTATION_ADAPTER.fromJson(reader); - result.addAnnotation(a.timestamp, a.value); + result.addAnnotation(a.timestamp(), a.value()); } reader.endArray(); break; @@ -104,27 +105,27 @@ public void toJson(JsonWriter writer, @Nullable Span value) throws IOException { }; static final JsonAdapter ANNOTATION_ADAPTER = new JsonAdapter() { - @Override @Nonnull - public Annotation fromJson(JsonReader reader) throws IOException { - Annotation.Builder result = Annotation.builder(); + @Override @Nonnull public Annotation fromJson(JsonReader reader) throws IOException { reader.beginObject(); + Long timestamp = null; + String value = null; while (reader.hasNext()) { switch (reader.nextName()) { case "timestamp": - result.timestamp(reader.nextLong()); + timestamp = reader.nextLong(); break; case "value": - result.value(reader.nextString()); - break; - case "endpoint": - result.endpoint(ENDPOINT_ADAPTER.fromJson(reader)); + value = reader.nextString(); break; default: reader.skipValue(); } } reader.endObject(); - return result.build(); + if (timestamp == null || value == null) { + throw new MalformedJsonException("Incomplete annotation at " + reader.getPath()); + } + return Annotation.create(timestamp, value); } @Override @@ -134,10 +135,10 @@ public void toJson(JsonWriter writer, @Nullable Annotation value) throws IOExcep }; static final JsonAdapter ENDPOINT_ADAPTER = new JsonAdapter() { - @Override @Nonnull - public Endpoint fromJson(JsonReader reader) throws IOException { - Endpoint.Builder result = Endpoint.builder().serviceName(""); + @Override @Nonnull public Endpoint fromJson(JsonReader reader) throws IOException { reader.beginObject(); + String serviceName = null, ipv4 = null, ipv6 = null; + Integer port = null; while (reader.hasNext()) { String nextName = reader.nextName(); if (reader.peek() == JsonReader.Token.NULL) { @@ -146,21 +147,26 @@ public Endpoint fromJson(JsonReader reader) throws IOException { } switch (nextName) { case "serviceName": - result.serviceName(reader.nextString()); + serviceName = reader.nextString(); break; case "ipv4": + ipv4 = reader.nextString(); + break; case "ipv6": - result.parseIp(reader.nextString()); + ipv6 = reader.nextString(); break; case "port": - result.port(reader.nextInt()); + port = reader.nextInt(); break; default: reader.skipValue(); } } reader.endObject(); - return result.build(); + if (serviceName == null && ipv4 == null && ipv6 == null && port == null) { + throw new MalformedJsonException("Incomplete endpoint at " + reader.getPath()); + } + return Endpoint.newBuilder().serviceName(serviceName).ip(ipv4).ip(ipv6).port(port).build(); } @Override diff --git a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/LegacyJsonAdapters.java b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/LegacyJsonAdapters.java index bf9471b9fbb..fc14b70c32e 100644 --- a/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/LegacyJsonAdapters.java +++ b/zipkin-storage/elasticsearch-http/src/main/java/zipkin/storage/elasticsearch/http/LegacyJsonAdapters.java @@ -18,16 +18,18 @@ import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; import java.io.IOException; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import okio.Buffer; import okio.ByteString; +import zipkin.Annotation; import zipkin.BinaryAnnotation; +import zipkin.Endpoint; import zipkin.Span; import zipkin.internal.Util; import static zipkin.internal.Util.UTF_8; import static zipkin.internal.Util.lowerHexToUnsignedLong; -import static zipkin.storage.elasticsearch.http.JsonAdapters.ENDPOINT_ADAPTER; final class LegacyJsonAdapters { static final JsonAdapter SPAN_ADAPTER = new JsonAdapter() { @@ -67,7 +69,7 @@ public Span fromJson(JsonReader reader) throws IOException { case "annotations": reader.beginArray(); while (reader.hasNext()) { - result.addAnnotation(JsonAdapters.ANNOTATION_ADAPTER.fromJson(reader)); + result.addAnnotation(ANNOTATION_ADAPTER.fromJson(reader)); } reader.endArray(); break; @@ -176,4 +178,70 @@ public void toJson(JsonWriter writer, @Nullable BinaryAnnotation value) throws I throw new UnsupportedOperationException(); } }; + + static final JsonAdapter ANNOTATION_ADAPTER = new JsonAdapter() { + @Override @Nonnull + public Annotation fromJson(JsonReader reader) throws IOException { + Annotation.Builder result = Annotation.builder(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.nextName()) { + case "timestamp": + result.timestamp(reader.nextLong()); + break; + case "value": + result.value(reader.nextString()); + break; + case "endpoint": + result.endpoint(ENDPOINT_ADAPTER.fromJson(reader)); + break; + default: + reader.skipValue(); + } + } + reader.endObject(); + return result.build(); + } + + @Override + public void toJson(JsonWriter writer, @Nullable Annotation value) throws IOException { + throw new UnsupportedOperationException(); + } + }; + + static final JsonAdapter ENDPOINT_ADAPTER = new JsonAdapter() { + @Override @Nonnull + public Endpoint fromJson(JsonReader reader) throws IOException { + Endpoint.Builder result = Endpoint.builder().serviceName(""); + reader.beginObject(); + while (reader.hasNext()) { + String nextName = reader.nextName(); + if (reader.peek() == JsonReader.Token.NULL) { + reader.skipValue(); + continue; + } + switch (nextName) { + case "serviceName": + result.serviceName(reader.nextString()); + break; + case "ipv4": + case "ipv6": + result.parseIp(reader.nextString()); + break; + case "port": + result.port(reader.nextInt()); + break; + default: + reader.skipValue(); + } + } + reader.endObject(); + return result.build(); + } + + @Override + public void toJson(JsonWriter writer, @Nullable Endpoint value) throws IOException { + throw new UnsupportedOperationException(); + } + }.nullSafe(); } 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 a38df3cea6d..d3e8ddd64fc 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 @@ -22,12 +22,11 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import zipkin.TestObjects; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import zipkin.internal.v2.Span.Kind; -import zipkin.internal.v2.codec.Decoder; -import zipkin.internal.v2.codec.Encoder; -import zipkin.internal.v2.codec.MessageEncoder; +import zipkin.internal.v2.codec.BytesDecoder; +import zipkin.internal.v2.codec.BytesEncoder; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @@ -36,6 +35,9 @@ import static zipkin.storage.elasticsearch.http.ElasticsearchHttpSpanConsumer.prefixWithTimestampMillisAndQuery; public class ElasticsearchHttpSpanConsumerTest { + static final Endpoint WEB_ENDPOINT = Endpoint.newBuilder().serviceName("web").build(); + static final Endpoint APP_ENDPOINT = Endpoint.newBuilder().serviceName("app").build(); + @Rule public MockWebServer es = new MockWebServer(); ElasticsearchHttpStorage storage = ElasticsearchHttpStorage.builder() @@ -62,7 +64,7 @@ public void close() throws IOException { @Test public void addsTimestamp_millisIntoJson() throws Exception { es.enqueue(new MockResponse()); - Span span = Span.builder().traceId(20L).id(20L).name("get") + Span span = Span.newBuilder().traceId("20").id("20").name("get") .timestamp(TODAY * 1000).build(); accept(span); @@ -72,8 +74,8 @@ public void close() throws IOException { } @Test public void prefixWithTimestampMillisAndQuery_skipsWhenNoData() throws Exception { - Span span = Span.builder().traceId(20L).id(22L).name("").parentId(21L).timestamp(0L) - .localEndpoint(TestObjects.WEB_ENDPOINT) + Span span = Span.newBuilder().traceId("20").id("22").name("").parentId("21").timestamp(0L) + .localEndpoint(WEB_ENDPOINT) .kind(Kind.CLIENT) .build(); @@ -84,8 +86,8 @@ public void close() throws IOException { } @Test public void prefixWithTimestampMillisAndQuery_addsTimestampMillis() throws Exception { - Span span = Span.builder().traceId(20L).id(22L).name("").parentId(21L).timestamp(1L) - .localEndpoint(TestObjects.WEB_ENDPOINT) + Span span = Span.newBuilder().traceId("20").id("22").name("").parentId("21").timestamp(1L) + .localEndpoint(WEB_ENDPOINT) .kind(Kind.CLIENT) .build(); @@ -96,8 +98,8 @@ public void close() throws IOException { } @Test public void prefixWithTimestampMillisAndQuery_addsAnnotationQuery() throws Exception { - Span span = Span.builder().traceId(20L).id(22L).name("").parentId(21L) - .localEndpoint(TestObjects.WEB_ENDPOINT) + Span span = Span.newBuilder().traceId("20").id("22").name("").parentId("21") + .localEndpoint(WEB_ENDPOINT) .addAnnotation(1L, "\"foo") .build(); @@ -108,8 +110,8 @@ public void close() throws IOException { } @Test public void prefixWithTimestampMillisAndQuery_addsAnnotationQueryTags() throws Exception { - Span span = Span.builder().traceId(20L).id(22L).name("").parentId(21L) - .localEndpoint(TestObjects.WEB_ENDPOINT) + Span span = Span.newBuilder().traceId("20").id("22").name("").parentId("21") + .localEndpoint(WEB_ENDPOINT) .putTag("\"foo", "\"bar") .build(); @@ -120,21 +122,17 @@ public void close() throws IOException { } @Test public void prefixWithTimestampMillisAndQuery_readable() throws Exception { - Span span = Span.builder().traceId(20L).id(20L).name("get") + Span span = Span.newBuilder().traceId("20").id("20").name("get") .timestamp(TODAY * 1000).build(); - byte[] message = MessageEncoder.JSON_BYTES.encode(asList( - prefixWithTimestampMillisAndQuery(span, span.timestamp()) - )); - - assertThat(Decoder.JSON.decodeList(message)) - .containsOnly(span); // ignores timestamp_millis field + assertThat(BytesDecoder.JSON.decode(prefixWithTimestampMillisAndQuery(span, span.timestamp()))) + .isEqualTo(span); // ignores timestamp_millis field } @Test public void doesntWriteDocumentId() throws Exception { es.enqueue(new MockResponse()); - accept(Span.builder().traceId(1L).id(1L).name("foo").build()); + accept(Span.newBuilder().traceId("1").id("1").name("foo").build()); RecordedRequest request = es.takeRequest(); assertThat(request.getBody().readByteString().utf8()) @@ -144,26 +142,26 @@ public void close() throws IOException { @Test public void writesSpanNaturallyWhenNoTimestamp() throws Exception { es.enqueue(new MockResponse()); - Span span = Span.builder().traceId(1L).id(1L).name("foo").build(); - accept(Span.builder().traceId(1L).id(1L).name("foo").build()); + Span span = Span.newBuilder().traceId("1").id("1").name("foo").build(); + accept(Span.newBuilder().traceId("1").id("1").name("foo").build()); assertThat(es.takeRequest().getBody().readByteString().utf8()) - .contains("\n" + new String(Encoder.JSON.encode(span), UTF_8) + "\n"); + .contains("\n" + new String(BytesEncoder.JSON.encode(span), UTF_8) + "\n"); } @Test public void traceIsSearchableByServerServiceName() throws Exception { es.enqueue(new MockResponse()); - Span clientSpan = Span.builder().traceId(20L).id(22L).name("").parentId(21L) + Span clientSpan = Span.newBuilder().traceId("20").id("22").name("").parentId("21") .timestamp(1000L) .kind(Kind.CLIENT) - .localEndpoint(TestObjects.WEB_ENDPOINT) + .localEndpoint(WEB_ENDPOINT) .build(); - Span serverSpan = Span.builder().traceId(20L).id(22L).name("get").parentId(21L) + Span serverSpan = Span.newBuilder().traceId("20").id("22").name("get").parentId("21") .timestamp(2000L) .kind(Kind.SERVER) - .localEndpoint(TestObjects.APP_ENDPOINT) + .localEndpoint(APP_ENDPOINT) .build(); accept(serverSpan, clientSpan); @@ -185,7 +183,7 @@ public void close() throws IOException { es.enqueue(new MockResponse()); - accept(Span.builder().traceId(1L).id(1L).name("foo").build()); + accept(Span.newBuilder().traceId("1").id("1").name("foo").build()); RecordedRequest request = es.takeRequest(); assertThat(request.getPath()) @@ -195,8 +193,8 @@ public void close() throws IOException { @Test public void choosesTypeSpecificIndex() throws Exception { es.enqueue(new MockResponse()); - Span span = Span.builder().traceId(1L).id(2L).parentId(1L).name("s") - .localEndpoint(TestObjects.APP_ENDPOINT) + Span span = Span.newBuilder().traceId("1").id("2").parentId("1").name("s") + .localEndpoint(APP_ENDPOINT) .addAnnotation(TimeUnit.DAYS.toMicros(365) /* 1971-01-01 */, "foo") .build(); diff --git a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStoreTest.java b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStoreTest.java index 2b7bbf2f2b2..38548bf840e 100644 --- a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStoreTest.java +++ b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/ElasticsearchHttpSpanStoreTest.java @@ -41,6 +41,25 @@ public class ElasticsearchHttpSpanStoreTest { storage.close(); } + @Test public void doesntTruncateTraceIdByDefault() throws Exception { + es.enqueue(new MockResponse()); + spanStore.getTrace("48fec942f3e78b893041d36dc43227fd").execute(); + + assertThat(es.takeRequest().getBody().readUtf8()) + .contains("\"traceId\":\"48fec942f3e78b893041d36dc43227fd\""); + } + + @Test public void truncatesTraceIdTo16CharsWhenNotStrict() throws Exception { + storage = storage.toBuilder().strictTraceId(false).build(); + spanStore = new ElasticsearchHttpSpanStore(storage); + + es.enqueue(new MockResponse()); + spanStore.getTrace("48fec942f3e78b893041d36dc43227fd").execute(); + + assertThat(es.takeRequest().getBody().readUtf8()) + .contains("\"traceId\":\"3041d36dc43227fd\""); + } + @Test public void serviceNames_defaultsTo24HrsAgo_6x() throws Exception { es.enqueue(new MockResponse().setBody(SERVICE_NAMES)); spanStore.getServiceNames().execute(); diff --git a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/InternalForTests.java b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/InternalForTests.java index 580def275d0..ed6945c781d 100644 --- a/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/InternalForTests.java +++ b/zipkin-storage/elasticsearch-http/src/test/java/zipkin/storage/elasticsearch/http/InternalForTests.java @@ -18,7 +18,6 @@ import java.util.List; import zipkin.Codec; import zipkin.DependencyLink; -import zipkin.internal.CallbackCaptor; import static zipkin.storage.elasticsearch.http.ElasticsearchHttpSpanStore.DEPENDENCY; 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 262c7e465e5..0d39dd62c51 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 @@ -18,13 +18,12 @@ import org.junit.Test; import zipkin.Codec; import zipkin.DependencyLink; -import zipkin.Endpoint; import zipkin.TestObjects; import zipkin.internal.ApplyTimestampAndDuration; -import zipkin.internal.Util; import zipkin.internal.V2SpanConverter; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Encoder; +import zipkin.internal.v2.codec.BytesEncoder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -134,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(Encoder.JSON.encode(span2)); + bytes.write(BytesEncoder.JSON.encode(span2)); assertThat(SPAN_ADAPTER.fromJson(bytes)) .isEqualTo(span2); } @@ -146,8 +145,8 @@ public void span_roundTrip() throws IOException { @Test public void span_specialCharsInJson() throws IOException { // service name is surrounded by control characters - Endpoint e = Endpoint.create(new String(new char[] {0, 'a', 1}), 0); - Span worstSpanInTheWorld = Span.builder().traceId(1L).id(1L) + Endpoint e = Endpoint.newBuilder().serviceName(new String(new char[] {0, 'a', 1})).build(); + Span worstSpanInTheWorld = Span.newBuilder().traceId("1").id("1") // name is terrible .name(new String(new char[] {'"', '\\', '\t', '\b', '\n', '\r', '\f'})) .localEndpoint(e) @@ -159,7 +158,7 @@ public void span_specialCharsInJson() throws IOException { .build(); Buffer bytes = new Buffer(); - bytes.write(Encoder.JSON.encode(worstSpanInTheWorld)); + bytes.write(BytesEncoder.JSON.encode(worstSpanInTheWorld)); assertThat(SPAN_ADAPTER.fromJson(bytes)) .isEqualTo(worstSpanInTheWorld); } @@ -177,7 +176,7 @@ public void span_endpointHighPort() throws IOException { + "}"; assertThat(SPAN_ADAPTER.fromJson(json).localEndpoint()) - .isEqualTo(Endpoint.builder().serviceName("service").port(65535).build()); + .isEqualTo(Endpoint.newBuilder().serviceName("service").port(65535).build()); } @Test @@ -192,7 +191,7 @@ public void span_noServiceName() throws IOException { + "}"; assertThat(SPAN_ADAPTER.fromJson(json).localEndpoint()) - .isEqualTo(Endpoint.builder().serviceName("").port(65535).build()); + .isEqualTo(Endpoint.newBuilder().serviceName("").port(65535).build()); } @Test @@ -208,7 +207,7 @@ public void span_nullServiceName() throws IOException { + "}"; assertThat(SPAN_ADAPTER.fromJson(json).localEndpoint()) - .isEqualTo(Endpoint.builder().serviceName("").port(65535).build()); + .isEqualTo(Endpoint.newBuilder().serviceName("").port(65535).build()); } @Test @@ -226,7 +225,7 @@ public void span_readsTraceIdHighFromTraceIdField() throws IOException { assertThat(JsonAdapters.SPAN_ADAPTER.fromJson(with128BitTraceId)) .isEqualTo(JsonAdapters.SPAN_ADAPTER.fromJson(withLower64bitsTraceId).toBuilder() - .traceIdHigh(Util.lowerHexToUnsignedLong("48485a3953bb6124")).build()); + .traceId("48485a3953bb61246b221d5bc9e6496c").build()); } @Test diff --git a/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkV2SpanIterator.java b/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkV2SpanIterator.java index 5d6f98af4b6..7ec9a1c3a7d 100644 --- a/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkV2SpanIterator.java +++ b/zipkin-storage/mysql/src/main/java/zipkin/storage/mysql/DependencyLinkV2SpanIterator.java @@ -19,8 +19,8 @@ import org.jooq.TableField; import zipkin.BinaryAnnotation.Type; import zipkin.Constants; -import zipkin.Endpoint; import zipkin.internal.PeekingIterator; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import zipkin.storage.mysql.internal.generated.tables.ZipkinSpans; @@ -30,6 +30,7 @@ import static zipkin.Constants.SERVER_ADDR; import static zipkin.Constants.SERVER_RECV; import static zipkin.internal.Util.equal; +import static zipkin.internal.Util.toLowerHex; import static zipkin.storage.mysql.internal.generated.tables.ZipkinAnnotations.ZIPKIN_ANNOTATIONS; /** @@ -37,9 +38,8 @@ * short-cuts to require less data. For example, it folds shared RPC spans into one, and doesn't * include tags, non-core annotations or time units. * - *

Out-of-date schemas may be missing the trace_id_high field. When present, this becomes {@link - * Span#traceIdHigh()} used as the left-most 16 characters of the traceId in logging - * statements. + *

Out-of-date schemas may be missing the trace_id_high field. When present, the {@link + * Span#traceId()} could be 32 characters in logging statements. */ final class DependencyLinkV2SpanIterator implements Iterator { @@ -132,11 +132,11 @@ public Span next() { // Skip the client side, so it isn't mistaken for a loopback request if (equal(saService, caService)) caService = null; - Span.Builder result = Span.builder() - .traceIdHigh(traceIdHi != null ? traceIdHi : 0L) - .traceId(traceIdLo) - .parentId(row.getValue(ZipkinSpans.ZIPKIN_SPANS.PARENT_ID)) - .id(spanId); + Long parentId = row.getValue(ZipkinSpans.ZIPKIN_SPANS.PARENT_ID); + Span.Builder result = Span.newBuilder() + .traceId(toLowerHex(traceIdHi != null ? traceIdHi : 0L, traceIdLo)) + .parentId(parentId != null ? toLowerHex(parentId) : null) + .id(toLowerHex(spanId)); if (error) { result.putTag(Constants.ERROR, "" /* actual value doesn't matter */); @@ -176,6 +176,6 @@ static long traceIdHigh(PeekingIterator delegate) { } static Endpoint ep(@Nullable String serviceName) { - return serviceName != null ? Endpoint.builder().serviceName(serviceName).build() : null; + return serviceName != null ? Endpoint.newBuilder().serviceName(serviceName).build() : null; } } diff --git a/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkV2SpanIteratorTest.java b/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkV2SpanIteratorTest.java index 55e7be4159d..d404b142794 100644 --- a/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkV2SpanIteratorTest.java +++ b/zipkin-storage/mysql/src/test/java/zipkin/storage/mysql/DependencyLinkV2SpanIteratorTest.java @@ -55,8 +55,8 @@ public class DependencyLinkV2SpanIteratorTest { Span span = iterator.next(); assertThat(span.kind()).isNull(); - assertThat(span.localEndpoint().serviceName).isEqualTo("service1"); - assertThat(span.remoteEndpoint().serviceName).isEqualTo("service2"); + assertThat(span.localServiceName()).isEqualTo("service1"); + assertThat(span.remoteServiceName()).isEqualTo("service2"); } /** The linker is biased towards server spans, or client spans that know the peer localEndpoint(). */ @@ -79,7 +79,7 @@ public class DependencyLinkV2SpanIteratorTest { Span span = iterator.next(); assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); - assertThat(span.localEndpoint().serviceName).isEqualTo("service"); + assertThat(span.localServiceName()).isEqualTo("service"); assertThat(span.remoteEndpoint()).isNull(); } @@ -116,8 +116,8 @@ public class DependencyLinkV2SpanIteratorTest { Span span = iterator.next(); assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); - assertThat(span.localEndpoint().serviceName).isEqualTo("service2"); - assertThat(span.remoteEndpoint().serviceName).isEqualTo("service1"); + assertThat(span.localServiceName()).isEqualTo("service2"); + assertThat(span.remoteServiceName()).isEqualTo("service1"); } /** @@ -132,8 +132,8 @@ public class DependencyLinkV2SpanIteratorTest { Span span = iterator.next(); assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); - assertThat(span.localEndpoint().serviceName).isEqualTo("service2"); - assertThat(span.remoteEndpoint().serviceName).isEqualTo("service1"); + assertThat(span.localServiceName()).isEqualTo("service2"); + assertThat(span.remoteServiceName()).isEqualTo("service1"); } /** {@link Constants#CLIENT_ADDR} is more authoritative than {@link Constants#CLIENT_SEND} */ @@ -146,8 +146,8 @@ public class DependencyLinkV2SpanIteratorTest { Span span = iterator.next(); assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); - assertThat(span.localEndpoint().serviceName).isEqualTo("service2"); - assertThat(span.remoteEndpoint().serviceName).isEqualTo("service1"); + assertThat(span.localServiceName()).isEqualTo("service2"); + assertThat(span.remoteServiceName()).isEqualTo("service1"); } /** Finagle labels two sides of the same socket "ca", Type.BOOL.value, "sa" with the local endpoint name */ @@ -162,7 +162,7 @@ public class DependencyLinkV2SpanIteratorTest { // When there's no "sr" annotation, we assume it is a client. assertThat(span.kind()).isEqualTo(Span.Kind.CLIENT); assertThat(span.localEndpoint()).isNull(); - assertThat(span.remoteEndpoint().serviceName).isEqualTo("service"); + assertThat(span.remoteServiceName()).isEqualTo("service"); } @Test public void specialCasesFinagleLocalSocketLabeling_server() { @@ -175,7 +175,7 @@ public class DependencyLinkV2SpanIteratorTest { // When there is an "sr" annotation, we know it is a server assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); - assertThat(span.localEndpoint().serviceName).isEqualTo("service"); + assertThat(span.localServiceName()).isEqualTo("service"); assertThat(span.remoteEndpoint()).isNull(); } @@ -190,7 +190,7 @@ public class DependencyLinkV2SpanIteratorTest { Span span = iterator.next(); assertThat(span.kind()).isEqualTo(Span.Kind.SERVER); - assertThat(span.localEndpoint().serviceName).isEqualTo("service1"); + assertThat(span.localServiceName()).isEqualTo("service1"); assertThat(span.remoteEndpoint()).isNull(); } diff --git a/zipkin/src/main/java/zipkin/collector/Collector.java b/zipkin/src/main/java/zipkin/collector/Collector.java index 4f381f32fe3..7e4180a55ac 100644 --- a/zipkin/src/main/java/zipkin/collector/Collector.java +++ b/zipkin/src/main/java/zipkin/collector/Collector.java @@ -23,7 +23,7 @@ import zipkin.internal.V2SpanConverter; import zipkin.internal.V2StorageComponent; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Decoder; +import zipkin.internal.v2.codec.BytesDecoder; import zipkin.storage.Callback; import zipkin.storage.StorageComponent; @@ -108,7 +108,7 @@ public void acceptSpans(byte[] serializedSpans, SpanDecoder decoder, Callback apply(Logger logger, List spans) { } rootSpanId = next.id; } - if (!treeBuilder.addNode(next.parentId, next.id, next)) dataError = true; + if (!treeBuilder.addNode( + next.parentId != null ? toLowerHex(next.parentId) : null, + toLowerHex(next.id), + next + )) { + dataError = true; + } } if (rootSpanId == null) { diff --git a/zipkin/src/main/java/zipkin/internal/DependencyLinker.java b/zipkin/src/main/java/zipkin/internal/DependencyLinker.java index 1666cb2d211..185efa461e6 100644 --- a/zipkin/src/main/java/zipkin/internal/DependencyLinker.java +++ b/zipkin/src/main/java/zipkin/internal/DependencyLinker.java @@ -100,7 +100,7 @@ public DependencyLinker putTrace(Iterator spans) { Span first = spans.next(); Node.TreeBuilder builder = - new Node.TreeBuilder<>(logger, MERGE_RPC, first.traceIdString()); + new Node.TreeBuilder<>(logger, MERGE_RPC, first.traceId()); builder.addNode(first.parentId(), first.id(), first); while (spans.hasNext()) { Span next = spans.next(); @@ -192,7 +192,7 @@ public DependencyLinker putTrace(Iterator spans) { // When an RPC is split between spans, we skip the child (server side). If our parent is a // client, we need to check it for errors. if (!isError && Kind.CLIENT.equals(rpcAncestor.kind()) && - currentSpan.parentId() != null && currentSpan.parentId() == rpcAncestor.id()) { + currentSpan.parentId() != null && currentSpan.parentId().equals(rpcAncestor.id())) { isError = rpcAncestor.tags().containsKey(ERROR); } } diff --git a/zipkin/src/main/java/zipkin/internal/JsonCodec.java b/zipkin/src/main/java/zipkin/internal/JsonCodec.java index ac97af89b56..8763a582535 100644 --- a/zipkin/src/main/java/zipkin/internal/JsonCodec.java +++ b/zipkin/src/main/java/zipkin/internal/JsonCodec.java @@ -487,20 +487,24 @@ public byte[] writeSpans(List value) { @Override public byte[] writeTraces(List> traces) { + return writeNestedList(SPAN_WRITER, traces); + } + + public static byte[] writeNestedList(Buffer.Writer writer, List> traces) { // Get the encoded size of the nested list so that we don't need to grow the buffer int sizeInBytes = overheadInBytes(traces); for (int i = 0, length = traces.size(); i < length; i++) { - List spans = traces.get(i); + List spans = traces.get(i); sizeInBytes += overheadInBytes(spans); for (int j = 0, jLength = spans.size(); j < jLength; j++) { - sizeInBytes += SPAN_WRITER.sizeInBytes(spans.get(j)); + sizeInBytes += writer.sizeInBytes(spans.get(j)); } } Buffer out = new Buffer(sizeInBytes); out.writeByte('['); // start list of traces for (int i = 0, length = traces.size(); i < length; i++) { - writeList(SPAN_WRITER, traces.get(i), out); + writeList(writer, traces.get(i), out); if (i + 1 < length) out.writeByte(','); } out.writeByte(']'); // stop list of traces @@ -638,7 +642,7 @@ public byte[] writeStrings(List value) { return writeList(STRING_WRITER, value); } - static T read(JsonReaderAdapter adapter, byte[] bytes) { + public static T read(JsonReaderAdapter adapter, byte[] bytes) { checkArgument(bytes.length > 0, "Empty input reading %s", adapter); try { return adapter.fromJson(jsonReader(bytes)); @@ -715,7 +719,7 @@ static int overheadInBytes(List value) { return sizeInBytes; } - static byte[] writeList(Buffer.Writer writer, List value) { + public static byte[] writeList(Buffer.Writer writer, List value) { if (value.isEmpty()) return new byte[] {'[', ']'}; Buffer result = new Buffer(JsonCodec.sizeInBytes(writer, value)); writeList(writer, value, result); diff --git a/zipkin/src/main/java/zipkin/internal/Node.java b/zipkin/src/main/java/zipkin/internal/Node.java index 1b5844a7c26..541a1384dac 100644 --- a/zipkin/src/main/java/zipkin/internal/Node.java +++ b/zipkin/src/main/java/zipkin/internal/Node.java @@ -29,7 +29,6 @@ import static java.util.logging.Level.FINE; import static zipkin.internal.Util.checkArgument; import static zipkin.internal.Util.checkNotNull; -import static zipkin.internal.Util.toLowerHex; /** * Convenience type representing a tree. This is here because multiple facets in zipkin require @@ -141,29 +140,29 @@ static final class TreeBuilder { this.traceId = traceId; } - Long rootId = null; + String rootId = null; Node rootNode = null; // Nodes representing the trace tree - Map> idToNode = new LinkedHashMap<>(); + Map> idToNode = new LinkedHashMap<>(); // Collect the parent-child relationships between all spans. - Map idToParent = new LinkedHashMap<>(idToNode.size()); + Map idToParent = new LinkedHashMap<>(idToNode.size()); /** Returns false after logging to FINE if the value couldn't be added */ - public boolean addNode(@Nullable Long parentId, long id, V value) { + public boolean addNode(@Nullable String parentId, String id, V value) { if (parentId == null) { if (rootId != null) { if (logger.isLoggable(FINE)) { logger.fine(format( "attributing span missing parent to root: traceId=%s, rootSpanId=%s, spanId=%s", - traceId, toLowerHex(rootId), toLowerHex(id))); + traceId, rootId, id)); } } else { rootId = id; } - } else if (parentId == id) { + } else if (parentId.equals(id)) { if (logger.isLoggable(FINE)) { logger.fine( - format("skipping circular dependency: traceId=%s, spanId=%s", traceId, toLowerHex(id))); + format("skipping circular dependency: traceId=%s, spanId=%s", traceId, id)); } return false; } @@ -174,7 +173,7 @@ public boolean addNode(@Nullable Long parentId, long id, V value) { if (parentId == null && rootNode == null) { rootNode = node; rootId = id; - } else if (parentId == null && rootId == id) { + } else if (parentId == null && rootId.equals(id)) { rootNode.value(mergeFunction.merge(rootNode.value, node.value)); } else { Node previous = idToNode.put(id, node); @@ -187,7 +186,7 @@ public boolean addNode(@Nullable Long parentId, long id, V value) { /** Builds a tree from calls to {@link #addNode}, or returns an empty tree. */ public Node build() { // Materialize the tree using parent - child relationships - for (Map.Entry entry : idToParent.entrySet()) { + for (Map.Entry entry : idToParent.entrySet()) { Node node = idToNode.get(entry.getKey()); Node parent = idToNode.get(entry.getValue()); if (parent == null) { // handle headless diff --git a/zipkin/src/main/java/zipkin/internal/V2Collector.java b/zipkin/src/main/java/zipkin/internal/V2Collector.java index 683df1f99a2..f763a6e12f6 100644 --- a/zipkin/src/main/java/zipkin/internal/V2Collector.java +++ b/zipkin/src/main/java/zipkin/internal/V2Collector.java @@ -19,12 +19,12 @@ import zipkin.collector.CollectorMetrics; import zipkin.collector.CollectorSampler; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Decoder; +import zipkin.internal.v2.codec.BytesDecoder; import zipkin.storage.Callback; import static zipkin.internal.Util.checkNotNull; -public final class V2Collector extends Collector, Span> { +public final class V2Collector extends Collector, Span> { final V2StorageComponent storage; final CollectorSampler sampler; @@ -36,16 +36,16 @@ public V2Collector(Logger logger, @Nullable CollectorMetrics metrics, } @Override - public void acceptSpans(byte[] serializedSpans, Decoder decoder, Callback callback) { + public void acceptSpans(byte[] serializedSpans, BytesDecoder decoder, Callback callback) { super.acceptSpans(serializedSpans, decoder, callback); } - @Override protected List decodeList(Decoder decoder, byte[] serialized) { + @Override protected List decodeList(BytesDecoder decoder, byte[] serialized) { return decoder.decodeList(serialized); } @Override protected boolean isSampled(Span span) { - return sampler.isSampled(span.traceId(), span.debug()); + return sampler.isSampled(Util.lowerHexToUnsignedLong(span.traceId()), span.debug()); } @Override protected void record(List sampled, Callback callback) { @@ -53,6 +53,6 @@ public void acceptSpans(byte[] serializedSpans, Decoder decoder, Callback< } @Override protected String idString(Span span) { - return span.idString(); + return span.traceId() + "/" + span.id(); } } diff --git a/zipkin/src/main/java/zipkin/internal/V2JsonSpanDecoder.java b/zipkin/src/main/java/zipkin/internal/V2JsonSpanDecoder.java index 60cc693514f..5a1fbba042d 100644 --- a/zipkin/src/main/java/zipkin/internal/V2JsonSpanDecoder.java +++ b/zipkin/src/main/java/zipkin/internal/V2JsonSpanDecoder.java @@ -18,7 +18,7 @@ import java.util.List; import zipkin.SpanDecoder; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Decoder; +import zipkin.internal.v2.codec.BytesDecoder; /** Decodes a span from zipkin v2 encoding */ public final class V2JsonSpanDecoder implements SpanDecoder { @@ -27,7 +27,7 @@ public final class V2JsonSpanDecoder implements SpanDecoder { } @Override public List readSpans(byte[] span) { - List span2s = Decoder.JSON.decodeList(span); + List span2s = BytesDecoder.JSON.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/V2SpanConverter.java b/zipkin/src/main/java/zipkin/internal/V2SpanConverter.java index 9634f3de46c..865b660313c 100644 --- a/zipkin/src/main/java/zipkin/internal/V2SpanConverter.java +++ b/zipkin/src/main/java/zipkin/internal/V2SpanConverter.java @@ -13,6 +13,8 @@ */ package zipkin.internal; +import java.net.Inet6Address; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -22,7 +24,7 @@ import zipkin.Annotation; import zipkin.BinaryAnnotation; import zipkin.Constants; -import zipkin.Endpoint; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import zipkin.internal.v2.Span.Kind; @@ -30,6 +32,7 @@ import static zipkin.Constants.CLIENT_ADDR; import static zipkin.Constants.LOCAL_COMPONENT; import static zipkin.Constants.SERVER_ADDR; +import static zipkin.internal.Util.lowerHexToUnsignedLong; import static zipkin.internal.Util.writeBase64Url; /** @@ -106,7 +109,7 @@ void processAnnotations(zipkin.Span source) { if (closeEnough(cs.endpoint, sr.endpoint)) { client.kind(Kind.CLIENT); // fork a new span for the server side - server = newSpanBuilder(source, sr.endpoint).kind(Kind.SERVER); + server = newSpanBuilder(source, convert(sr.endpoint)).kind(Kind.SERVER); } else { server = forEndpoint(source, sr.endpoint); } @@ -147,7 +150,7 @@ void processAnnotations(zipkin.Span source) { if (closeEnough(ms.endpoint, mr.endpoint)) { producer.kind(Kind.PRODUCER); // fork a new span for the consumer side - consumer = newSpanBuilder(source, mr.endpoint).kind(Kind.CONSUMER); + consumer = newSpanBuilder(source, convert(mr.endpoint)).kind(Kind.CONSUMER); } else { consumer = forEndpoint(source, mr.endpoint); } @@ -185,7 +188,7 @@ void maybeTimestampDuration(zipkin.Span source, Annotation begin, @Nullable Anno } void processBinaryAnnotations(zipkin.Span source) { - Endpoint ca = null, sa = null, ma = null; + zipkin.Endpoint ca = null, sa = null, ma = null; for (int i = 0, length = source.binaryAnnotations.size(); i < length; i++) { BinaryAnnotation b = source.binaryAnnotations.get(i); if (b.type == BOOL) { @@ -230,40 +233,41 @@ void processBinaryAnnotations(zipkin.Span source) { } if (cs != null && sa != null && !closeEnough(sa, cs.endpoint)) { - forEndpoint(source, cs.endpoint).remoteEndpoint(sa); + forEndpoint(source, cs.endpoint).remoteEndpoint(convert(sa)); } if (sr != null && ca != null && !closeEnough(ca, sr.endpoint)) { - forEndpoint(source, sr.endpoint).remoteEndpoint(ca); + forEndpoint(source, sr.endpoint).remoteEndpoint(convert(ca)); } if (ms != null && ma != null && !closeEnough(ma, ms.endpoint)) { - forEndpoint(source, ms.endpoint).remoteEndpoint(ma); + forEndpoint(source, ms.endpoint).remoteEndpoint(convert(ma)); } if (mr != null && ma != null && !closeEnough(ma, mr.endpoint)) { - forEndpoint(source, mr.endpoint).remoteEndpoint(ma); + forEndpoint(source, mr.endpoint).remoteEndpoint(convert(ma)); } // special-case when we are missing core annotations, but we have both address annotations if ((cs == null && sr == null) && (ca != null && sa != null)) { - forEndpoint(source, ca).remoteEndpoint(sa); + forEndpoint(source, ca).remoteEndpoint(convert(sa)); } } - Span.Builder forEndpoint(zipkin.Span source, @Nullable Endpoint e) { + Span.Builder forEndpoint(zipkin.Span source, @Nullable zipkin.Endpoint e) { if (e == null) return spans.get(0); // allocate missing endpoint data to first span + Endpoint converted = convert(e); for (int i = 0, length = spans.size(); i < length; i++) { Span.Builder next = spans.get(i); Endpoint nextLocalEndpoint = next.localEndpoint(); if (nextLocalEndpoint == null) { - next.localEndpoint(e); + next.localEndpoint(converted); return next; - } else if (closeEnough(nextLocalEndpoint, e)) { + } else if (closeEnough(convert(nextLocalEndpoint), e)) { return next; } } - return newSpanBuilder(source, e); + return newSpanBuilder(source, converted); } Span.Builder newSpanBuilder(zipkin.Span source, Endpoint e) { @@ -283,49 +287,52 @@ List build() { } } - static boolean closeEnough(Endpoint left, Endpoint right) { + static boolean closeEnough(zipkin.Endpoint left, zipkin.Endpoint right) { return left.serviceName.equals(right.serviceName); } static Span.Builder newBuilder(zipkin.Span source) { - return Span.builder() - .traceIdHigh(source.traceIdHigh) - .traceId(source.traceId) - .parentId(source.parentId) - .id(source.id) + return Span.newBuilder() + .traceId(source.traceIdString()) + .parentId(source.parentId != null ? Util.toLowerHex(source.parentId) : null) + .id(Util.toLowerHex(source.id)) .name(source.name) .debug(source.debug); } /** Converts the input, parsing {@link Span#kind()} into RPC annotations. */ public static zipkin.Span toSpan(Span in) { + String traceId = in.traceId(); zipkin.Span.Builder result = zipkin.Span.builder() - .traceIdHigh(in.traceIdHigh()) - .traceId(in.traceId()) - .parentId(in.parentId()) - .id(in.id()) + .traceId(lowerHexToUnsignedLong(traceId)) + .parentId(in.parentId() != null ? lowerHexToUnsignedLong(in.parentId()) : null) + .id(lowerHexToUnsignedLong(in.id())) .debug(in.debug()) - .name(in.name() == null ? "" : in.name()); // avoid a NPE + .name(in.name() != null ? in.name() : ""); // avoid a NPE - long timestamp = in.timestamp() == null ? 0L : in.timestamp(); - long duration = in.duration() == null ? 0L : in.duration(); - if (timestamp != 0L) { - result.timestamp(timestamp); - if (duration != 0L) result.duration(duration); + if (traceId.length() == 32) { + result.traceIdHigh(lowerHexToUnsignedLong(traceId, 0)); } + long startTs = in.timestamp() == null ? 0L : in.timestamp(); + Long endTs = in.duration() == null ? 0L : in.timestamp() + in.duration(); + if (startTs != 0L) { + result.timestamp(startTs); + result.duration(in.duration()); + } + + zipkin.Endpoint local = in.localEndpoint() != null ? convert(in.localEndpoint()) : null; + zipkin.Endpoint remote = in.remoteEndpoint() != null ? convert(in.remoteEndpoint()) : null; Kind kind = in.kind(); - Annotation cs = null, sr = null, ss = null, cr = null, ms = null, mr = null, ws = null, wr = - null; + Annotation + cs = null, sr = null, ss = null, cr = null, ms = null, mr = null, ws = null, wr = null; String remoteEndpointType = null; boolean wroteEndpoint = false; for (int i = 0, length = in.annotations().size(); i < length; i++) { - Annotation a = in.annotations().get(i); - if (in.localEndpoint() != null) { - a = a.toBuilder().endpoint(in.localEndpoint()).build(); - } + zipkin.internal.v2.Annotation input = in.annotations().get(i); + Annotation a = Annotation.create(input.timestamp(), input.value(), local); if (a.value.length() == 2) { if (a.value.equals(Constants.CLIENT_SEND)) { kind = Kind.CLIENT; @@ -365,39 +372,26 @@ public static zipkin.Span toSpan(Span in) { switch (kind) { case CLIENT: remoteEndpointType = Constants.SERVER_ADDR; - if (timestamp != 0L) { - cs = Annotation.create(timestamp, Constants.CLIENT_SEND, in.localEndpoint()); - } - if (duration != 0L) { - cr = Annotation.create(timestamp + duration, Constants.CLIENT_RECV, in.localEndpoint()); - } + if (startTs != 0L) cs = Annotation.create(startTs, Constants.CLIENT_SEND, local); + if (endTs != 0L) cr = Annotation.create(endTs, Constants.CLIENT_RECV, local); break; case SERVER: remoteEndpointType = Constants.CLIENT_ADDR; - if (timestamp != 0L) { - sr = Annotation.create(timestamp, Constants.SERVER_RECV, in.localEndpoint()); - } - if (duration != 0L) { - ss = Annotation.create(timestamp + duration, Constants.SERVER_SEND, in.localEndpoint()); - } + if (startTs != 0L) sr = Annotation.create(startTs, Constants.SERVER_RECV, local); + if (endTs != 0L) ss = Annotation.create(endTs, Constants.SERVER_SEND, local); break; case PRODUCER: remoteEndpointType = Constants.MESSAGE_ADDR; - if (timestamp != 0L) { - ms = Annotation.create(timestamp, Constants.MESSAGE_SEND, in.localEndpoint()); - } - if (duration != 0L) { - ws = Annotation.create(timestamp + duration, Constants.WIRE_SEND, in.localEndpoint()); - } + if (startTs != 0L) ms = Annotation.create(startTs, Constants.MESSAGE_SEND, local); + if (endTs != 0L) ws = Annotation.create(endTs, Constants.WIRE_SEND, local); break; case CONSUMER: remoteEndpointType = Constants.MESSAGE_ADDR; - if (timestamp != 0L && duration != 0L) { - wr = Annotation.create(timestamp, Constants.WIRE_RECV, in.localEndpoint()); - mr = - Annotation.create(timestamp + duration, Constants.MESSAGE_RECV, in.localEndpoint()); - } else if (timestamp != 0L) { - mr = Annotation.create(timestamp, Constants.MESSAGE_RECV, in.localEndpoint()); + if (startTs != 0L && endTs != 0L) { + wr = Annotation.create(startTs, Constants.WIRE_RECV, local); + mr = Annotation.create(endTs, Constants.MESSAGE_RECV, local); + } else if (startTs != 0L) { + mr = Annotation.create(startTs, Constants.MESSAGE_RECV, local); } break; default: @@ -407,8 +401,7 @@ public static zipkin.Span toSpan(Span in) { for (Map.Entry tag : in.tags().entrySet()) { wroteEndpoint = true; - result.addBinaryAnnotation( - BinaryAnnotation.create(tag.getKey(), tag.getValue(), in.localEndpoint())); + result.addBinaryAnnotation(BinaryAnnotation.create(tag.getKey(), tag.getValue(), local)); } if (cs != null @@ -428,23 +421,57 @@ public static zipkin.Span toSpan(Span in) { if (ms != null) result.addAnnotation(ms); if (mr != null) result.addAnnotation(mr); wroteEndpoint = true; - } else if (in.localEndpoint() != null && in.remoteEndpoint() != null) { + } else if (local != null && remote != null) { // special-case when we are missing core annotations, but we have both address annotations - result.addBinaryAnnotation(BinaryAnnotation.address(CLIENT_ADDR, in.localEndpoint())); + result.addBinaryAnnotation(BinaryAnnotation.address(CLIENT_ADDR, local)); wroteEndpoint = true; remoteEndpointType = SERVER_ADDR; } - if (remoteEndpointType != null && in.remoteEndpoint() != null) { - result.addBinaryAnnotation(BinaryAnnotation.address(remoteEndpointType, in.remoteEndpoint())); + if (remoteEndpointType != null && remote != null) { + result.addBinaryAnnotation(BinaryAnnotation.address(remoteEndpointType, remote)); } // don't report server-side timestamp on shared or incomplete spans if (Boolean.TRUE.equals(in.shared()) && sr != null) { result.timestamp(null).duration(null); } - if (in.localEndpoint() != null && !wroteEndpoint) { // create a dummy annotation - result.addBinaryAnnotation(BinaryAnnotation.create(LOCAL_COMPONENT, "", in.localEndpoint())); + if (local != null && !wroteEndpoint) { // create a dummy annotation + result.addBinaryAnnotation(BinaryAnnotation.create(LOCAL_COMPONENT, "", local)); + } + return result.build(); + } + + public static zipkin.internal.v2.Endpoint convert(zipkin.Endpoint input) { + zipkin.internal.v2.Endpoint.Builder result = zipkin.internal.v2.Endpoint.newBuilder() + .serviceName(input.serviceName) + .port(input.port != null ? input.port & 0xffff : null); + if (input.ipv4 != 0) { + result.parseIp(new StringBuilder() + .append(input.ipv4 >> 24 & 0xff).append('.') + .append(input.ipv4 >> 16 & 0xff).append('.') + .append(input.ipv4 >> 8 & 0xff).append('.') + .append(input.ipv4 & 0xff).toString()); + } + if (input.ipv6 != null) { + try { + result.parseIp(Inet6Address.getByAddress(input.ipv6)); + } catch (UnknownHostException e) { + throw new AssertionError(e); // ipv6 is fixed length, so shouldn't happen. + } + } + return result.build(); + } + + public static zipkin.Endpoint convert(Endpoint input) { + zipkin.Endpoint.Builder result = zipkin.Endpoint.builder() + .serviceName(input.serviceName() != null ? input.serviceName() : "") + .port(input.port() != null ? input.port() : 0); + if (input.ipv6() != null) { + result.parseIp(input.ipv6()); // parse first in case there's a mapped IP + } + if (input.ipv4() != null) { + result.parseIp(input.ipv4()); } return result.build(); } diff --git a/zipkin/src/main/java/zipkin/internal/V2SpanStoreAdapter.java b/zipkin/src/main/java/zipkin/internal/V2SpanStoreAdapter.java index ba871bab881..c1731e3986e 100644 --- a/zipkin/src/main/java/zipkin/internal/V2SpanStoreAdapter.java +++ b/zipkin/src/main/java/zipkin/internal/V2SpanStoreAdapter.java @@ -29,6 +29,8 @@ import zipkin.storage.Callback; import static zipkin.internal.GroupByTraceId.TRACE_DESCENDING; +import static zipkin.internal.Util.sortedList; +import static zipkin.internal.Util.toLowerHex; final class V2SpanStoreAdapter implements zipkin.storage.SpanStore, AsyncSpanStore { final SpanStore delegate; @@ -69,7 +71,7 @@ public void getTrace(long traceIdHigh, long traceIdLow, Callback> getTraceCall(long traceIdHigh, long traceIdLow) { - return delegate.getTrace(traceIdHigh, traceIdLow).map(getTraceMapper); + return delegate.getTrace(toLowerHex(traceIdHigh, traceIdLow)).map(getTraceMapper); } @Nullable @Override public List getRawTrace(long traceIdHigh, long traceIdLow) { @@ -87,12 +89,12 @@ public void getRawTrace(long traceIdHigh, long traceIdLow, } Call> getRawTraceCall(long traceIdHigh, long traceIdLow) { - return delegate.getTrace(traceIdHigh, traceIdLow).map(getRawTraceMapper); + return delegate.getTrace(toLowerHex(traceIdHigh, traceIdLow)).map(getRawTraceMapper); } @Override public List getServiceNames() { try { - return delegate.getServiceNames().execute(); + return sortedList(delegate.getServiceNames().execute()); } catch (IOException e) { throw Platform.get().uncheckedIOException(e); } @@ -104,7 +106,7 @@ Call> getRawTraceCall(long traceIdHigh, long traceIdLow) { @Override public List getSpanNames(String serviceName) { try { - return delegate.getSpanNames(serviceName).execute(); + return sortedList(delegate.getSpanNames(serviceName).execute()); } catch (IOException e) { throw Platform.get().uncheckedIOException(e); } diff --git a/zipkin/src/main/java/zipkin/internal/v2/Annotation.java b/zipkin/src/main/java/zipkin/internal/v2/Annotation.java new file mode 100644 index 00000000000..05ca7d9ecf9 --- /dev/null +++ b/zipkin/src/main/java/zipkin/internal/v2/Annotation.java @@ -0,0 +1,57 @@ +/** + * 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; + +import com.google.auto.value.AutoValue; +import java.io.Serializable; +import javax.annotation.concurrent.Immutable; + +/** + * Associates an event that explains latency with a timestamp. + * + *

Unlike log statements, annotations are often codes: Ex. {@link "cache.miss"}. + */ +@AutoValue +@Immutable +public abstract class Annotation implements Comparable, Serializable { // for Spark jobs + private static final long serialVersionUID = 0L; + + public static Annotation create(long timestamp, String value) { + return new AutoValue_Annotation(timestamp, value); + } + + /** + * Microseconds from epoch. + * + *

This value should be set directly by instrumentation, using the most precise value possible. + * For example, {@code gettimeofday} or multiplying {@link System#currentTimeMillis} by 1000. + */ + public abstract long timestamp(); + + /** + * Usually a short tag indicating an event, like {@code cache.miss} or {@code error} + */ + public abstract String value(); + + /** Compares by {@link #timestamp}, then {@link #value}. */ + @Override public int compareTo(Annotation that) { + if (this == that) return 0; + int byTimestamp = timestamp() < that.timestamp() ? -1 : timestamp() == that.timestamp() ? 0 : 1; + if (byTimestamp != 0) return byTimestamp; + return value().compareTo(that.value()); + } + + Annotation() { + } +} diff --git a/zipkin/src/main/java/zipkin/internal/v2/Endpoint.java b/zipkin/src/main/java/zipkin/internal/v2/Endpoint.java new file mode 100644 index 00000000000..27a72f931ac --- /dev/null +++ b/zipkin/src/main/java/zipkin/internal/v2/Endpoint.java @@ -0,0 +1,438 @@ +/** + * 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; + +import com.google.auto.value.AutoValue; +import java.io.Serializable; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.Locale; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** The network context of a node in the service graph. */ +@AutoValue +@Immutable +public abstract class Endpoint implements Serializable { // for Spark jobs + private static final long serialVersionUID = 0L; + + /** + * Lower-case label of this node in the service graph, such as "favstar". Leave absent if + * unknown. + * + *

This is a primary label for trace lookup and aggregation, so it should be intuitive and + * consistent. Many use a name from service discovery. + */ + @Nullable public abstract String serviceName(); + + /** + * The text representation of the primary IPv4 address associated with this a connection. Ex. + * 192.168.99.100 Absent if unknown. + */ + @Nullable public abstract String ipv4(); + + /** + * The text representation of the primary IPv6 address associated with this a connection. Ex. + * 2001:db8::c001 Absent if unknown. + * + *

Prefer using the {@link #ipv4()} field for mapped addresses. + */ + @Nullable public abstract String ipv6(); + + /** + * Port of the IP's socket or null, if not known. + * + * @see java.net.InetSocketAddress#getPort() + */ + @Nullable public abstract Integer port(); + + public abstract Builder toBuilder(); + + public static Builder newBuilder() { + return new AutoValue_Endpoint.Builder(); + } + + @AutoValue.Builder + public static abstract class Builder { + /** @see Endpoint#serviceName */ + public abstract Builder serviceName(@Nullable String serviceName); + + /** Chaining variant of {@link #parseIp(InetAddress)} */ + public Builder ip(@Nullable InetAddress addr) { + parseIp(addr); + return this; + } + + /** + * Returns true if {@link #ipv4(String)} or {@link #ipv6(String)} could be parsed from the + * input. + * + *

Returns boolean not this for conditional parsing. For example: + *

{@code
+     * if (!builder.parseIp(input.getHeader("X-Forwarded-For"))) {
+     *   builder.parseIp(input.getRemoteAddr());
+     * }
+     * }
+ * + * @see #parseIp(String) + */ + public final boolean parseIp(@Nullable InetAddress addr) { + if (addr == null) return false; + if (addr instanceof Inet4Address) { + ipv4(addr.getHostAddress()); + } else if (addr instanceof Inet6Address) { + byte[] addressBytes = addr.getAddress(); + String ipv4 = parseEmbeddedIPv4(addressBytes); + if (ipv4 != null) { + ipv4(ipv4); + } else { + ipv6(writeIpV6(addressBytes)); + } + } else { + return false; + } + return true; + } + + /** Chaining variant of {@link #parseIp(String)} */ + public Builder ip(@Nullable String ipString) { + parseIp(ipString); + return this; + } + + /** + * Returns true if {@link #ipv4(String)} or {@link #ipv6(String)} could be parsed from the + * input. + * + *

Returns boolean not this for conditional parsing. For example: + *

{@code
+     * if (!builder.parseIp(input.getHeader("X-Forwarded-For"))) {
+     *   builder.parseIp(input.getRemoteAddr());
+     * }
+     * }
+ * + * @see #parseIp(InetAddress) + */ + public final boolean parseIp(@Nullable String ipString) { + if (ipString == null || ipString.isEmpty()) return false; + IpFamily format = detectFamily(ipString); + if (format == IpFamily.IPv4) { + ipv4(ipString); + } else if (format == IpFamily.IPv4Embedded) { + ipv4(ipString.substring(ipString.lastIndexOf(':') + 1)); + } else if (format == IpFamily.IPv6) { + byte[] addressBytes = textToNumericFormatV6(ipString); + if (addressBytes == null) return false; + ipv6(writeIpV6(addressBytes)); // ensures consistent format + } else { + return false; + } + return true; + } + + // hidden to ensure input are parsed as otherwise equals comparisons like needed for clock skew + // won't work. + + /** @see Endpoint#ipv4 */ + abstract Builder ipv4(@Nullable String ipv4); + + /** @see Endpoint#ipv4 */ + abstract Builder ipv6(@Nullable String ipv6); + + /** + * Use this to set the port to an externally defined value. + * + * @param port port associated with the endpoint. zero coerces to null (unknown) + * @see Endpoint#port() + */ + public abstract Builder port(@Nullable Integer port); + + abstract @Nullable String serviceName(); + + abstract @Nullable Integer port(); + + abstract Endpoint autoBuild(); + + public final Endpoint build() { + String serviceName = serviceName(); + if (serviceName != null) { + if (serviceName.isEmpty()) { + serviceName(null); + } else { + serviceName(serviceName.toLowerCase(Locale.ROOT)); + } + } + Integer port = port(); + if (port != null) { + if (port < 0 || port > 0xffff) throw new IllegalArgumentException("invalid port " + port); + if (port == 0) port(null); + } + return autoBuild(); + } + + Builder() { + } + } + + static @Nullable String parseEmbeddedIPv4(byte[] ipv6) { + for (int i = 0; i < 10; i++) { // Embedded IPv4 addresses start with unset 80 bits + if (ipv6[i] != 0) return null; + } + + int flag = (ipv6[10] & 0xff) << 8 | (ipv6[11] & 0xff); + if (flag != 0 && flag != -1) return null; // IPv4-Compatible or IPv4-Mapped + + int o1 = ipv6[12] & 0xff, o2 = ipv6[13] & 0xff, o3 = ipv6[14] & 0xff, o4 = ipv6[15] & 0xff; + if (flag == 0 && o1 == 0 && o2 == 0 && o3 == 0 && o4 == 1) { + return null; // ::1 is localhost, not an embedded compat address + } + + return String.valueOf(o1) + '.' + o2 + '.' + o3 + '.' + o4; + } + + enum IpFamily { + Unknown, + IPv4, + IPv4Embedded, + IPv6 + } + + /** + * Adapted from code in {@code com.google.common.net.InetAddresses.ipStringToBytes}. This version + * separates detection from parsing and checks more carefully about embedded addresses. + */ + static IpFamily detectFamily(String ipString) { + boolean hasColon = false; + boolean hasDot = false; + for (int i = 0, length = ipString.length(); i < length; i++) { + char c = ipString.charAt(i); + if (c == '.') { + hasDot = true; + } else if (c == ':') { + if (hasDot) return IpFamily.Unknown; // Colons must not appear after dots. + hasColon = true; + } else if (notHex(c)) { + return IpFamily.Unknown; // Everything else must be a decimal or hex digit. + } + } + + // Now decide which address family to parse. + if (hasColon) { + if (hasDot) { + int lastColon = ipString.lastIndexOf(':'); + if (!isValidIpV4Address(ipString, lastColon + 1, ipString.length())) { + return IpFamily.Unknown; + } + if (lastColon == 1 && ipString.charAt(0) == ':') {// compressed like ::1.2.3.4 + return IpFamily.IPv4Embedded; + } + if (lastColon != 6 || ipString.charAt(0) != ':' || ipString.charAt(1) != ':') { + return IpFamily.Unknown; + } + for (int i = 2; i < 6; i++) { + char c = ipString.charAt(i); + if (c != 'f' && c != 'F' && c != '0') return IpFamily.Unknown; + } + return IpFamily.IPv4Embedded; + } + return IpFamily.IPv6; + } else if (hasDot && isValidIpV4Address(ipString, 0, ipString.length())) { + return IpFamily.IPv4; + } + return IpFamily.Unknown; + } + + private static boolean notHex(char c) { + return (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F'); + } + + private static final ThreadLocal IPV6_TO_STRING = new ThreadLocal() { + @Override protected char[] initialValue() { + return new char[39]; // maximum length of encoded ipv6 + } + }; + + static String writeIpV6(byte[] ipv6) { + int pos = 0; + char[] buf = IPV6_TO_STRING.get(); + + // Compress the longest string of zeros + int zeroCompressionIndex = -1; + int zeroCompressionLength = -1; + int zeroIndex = -1; + boolean allZeros = true; + for (int i = 0; i < ipv6.length; i += 2) { + if (ipv6[i] == 0 && ipv6[i + 1] == 0) { + if (zeroIndex < 0) zeroIndex = i; + continue; + } + allZeros = false; + if (zeroIndex >= 0) { + int zeroLength = i - zeroIndex; + if (zeroLength > zeroCompressionLength) { + zeroCompressionIndex = zeroIndex; + zeroCompressionLength = zeroLength; + } + zeroIndex = -1; + } + } + + // handle all zeros: 0:0:0:0:0:0:0:0 -> :: + if (allZeros) return "::"; + + // handle trailing zeros: 2001:0:0:4:0:0:0:0 -> 2001:0:0:4:: + if (zeroCompressionIndex == -1 && zeroIndex != -1) { + zeroCompressionIndex = zeroIndex; + zeroCompressionLength = 16 - zeroIndex; + } + + int i = 0; + while (i < ipv6.length) { + if (i == zeroCompressionIndex) { + buf[pos++] = ':'; + i += zeroCompressionLength; + if (i == ipv6.length) buf[pos++] = ':'; + continue; + } + if (i != 0) buf[pos++] = ':'; + + byte high = ipv6[i++]; + byte low = ipv6[i++]; + + // handle leading zeros: 2001:0:0:4:0000:0:0:8 -> 2001:0:0:4::8 + boolean leadingZero; + char val = HEX_DIGITS[(high >> 4) & 0xf]; + if (!(leadingZero = val == '0')) buf[pos++] = val; + val = HEX_DIGITS[high & 0xf]; + if (!(leadingZero = (leadingZero && val == '0'))) buf[pos++] = val; + val = HEX_DIGITS[(low >> 4) & 0xf]; + if (!(leadingZero && val == '0')) buf[pos++] = val; + buf[pos++] = HEX_DIGITS[low & 0xf]; + } + return new String(buf, 0, pos); + } + + static final char[] HEX_DIGITS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + // Begin code from com.google.common.net.InetAddresses 23 + private static final int IPV6_PART_COUNT = 8; + + @Nullable + private static byte[] textToNumericFormatV6(String ipString) { + // An address can have [2..8] colons, and N colons make N+1 parts. + String[] parts = ipString.split(":", IPV6_PART_COUNT + 2); + if (parts.length < 3 || parts.length > IPV6_PART_COUNT + 1) { + return null; + } + + // Disregarding the endpoints, find "::" with nothing in between. + // This indicates that a run of zeroes has been skipped. + int skipIndex = -1; + for (int i = 1; i < parts.length - 1; i++) { + if (parts[i].length() == 0) { + if (skipIndex >= 0) { + return null; // Can't have more than one :: + } + skipIndex = i; + } + } + + int partsHi; // Number of parts to copy from above/before the "::" + int partsLo; // Number of parts to copy from below/after the "::" + if (skipIndex >= 0) { + // If we found a "::", then check if it also covers the endpoints. + partsHi = skipIndex; + partsLo = parts.length - skipIndex - 1; + if (parts[0].length() == 0 && --partsHi != 0) { + return null; // ^: requires ^:: + } + if (parts[parts.length - 1].length() == 0 && --partsLo != 0) { + return null; // :$ requires ::$ + } + } else { + // Otherwise, allocate the entire address to partsHi. The endpoints + // could still be empty, but parseHextet() will check for that. + partsHi = parts.length; + partsLo = 0; + } + + // If we found a ::, then we must have skipped at least one part. + // Otherwise, we must have exactly the right number of parts. + int partsSkipped = IPV6_PART_COUNT - (partsHi + partsLo); + if (!(skipIndex >= 0 ? partsSkipped >= 1 : partsSkipped == 0)) { + return null; + } + + // Now parse the hextets into a byte array. + ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT); + try { + for (int i = 0; i < partsHi; i++) { + rawBytes.putShort(parseHextet(parts[i])); + } + for (int i = 0; i < partsSkipped; i++) { + rawBytes.putShort((short) 0); + } + for (int i = partsLo; i > 0; i--) { + rawBytes.putShort(parseHextet(parts[parts.length - i])); + } + } catch (NumberFormatException ex) { + return null; + } + return rawBytes.array(); + } + + private static short parseHextet(String ipPart) { + // Note: we already verified that this string contains only hex digits. + int hextet = Integer.parseInt(ipPart, 16); + if (hextet > 0xffff) { + throw new NumberFormatException(); + } + return (short) hextet; + } + // End code from com.google.common.net.InetAddresses 23 + + // Begin code from io.netty.util.NetUtil 4.1 + private static boolean isValidIpV4Address(String ip, int from, int toExcluded) { + int len = toExcluded - from; + int i; + return len <= 15 && len >= 7 && + (i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) && + (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && + (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && + isValidIpV4Word(ip, i + 1, toExcluded); + } + + private static boolean isValidIpV4Word(CharSequence word, int from, int toExclusive) { + int len = toExclusive - from; + char c0, c1, c2; + if (len < 1 || len > 3 || (c0 = word.charAt(from)) < '0') { + return false; + } + if (len == 3) { + return (c1 = word.charAt(from + 1)) >= '0' && + (c2 = word.charAt(from + 2)) >= '0' && + (c0 <= '1' && c1 <= '9' && c2 <= '9' || + c0 == '2' && c1 <= '5' && (c2 <= '5' || c1 < '5' && c2 <= '9')); + } + return c0 <= '9' && (len == 1 || isValidNumericChar(word.charAt(from + 1))); + } + + private static boolean isValidNumericChar(char c) { + return c >= '0' && c <= '9'; + } + // End code from io.netty.util.NetUtil 4.1 +} diff --git a/zipkin/src/main/java/zipkin/internal/v2/Span.java b/zipkin/src/main/java/zipkin/internal/v2/Span.java index c0de37eb1b0..d95a8ee17e7 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/Span.java +++ b/zipkin/src/main/java/zipkin/internal/v2/Span.java @@ -15,7 +15,9 @@ import com.google.auto.value.AutoValue; import java.io.Serializable; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -24,17 +26,9 @@ import java.util.TreeMap; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import zipkin.Annotation; import zipkin.Constants; -import zipkin.Endpoint; import zipkin.TraceKeys; -import zipkin.internal.v2.codec.Encoder; - -import static zipkin.internal.Util.UTF_8; -import static zipkin.internal.Util.checkNotNull; -import static zipkin.internal.Util.lowerHexToUnsignedLong; -import static zipkin.internal.Util.sortedList; -import static zipkin.internal.Util.writeHexLong; +import zipkin.internal.v2.codec.BytesEncoder; /** * A trace is a series of spans (often RPC calls) which form a latency tree. @@ -58,23 +52,36 @@ @AutoValue @Immutable public abstract class Span implements Serializable { // for Spark jobs - private static final long serialVersionUID = 0L; + static final Charset UTF_8 = Charset.forName("UTF-8"); - /** When non-zero, the trace containing this span uses 128-bit trace identifiers. */ - public abstract long traceIdHigh(); + private static final long serialVersionUID = 0L; - /** Unique 8-byte identifier for a trace, set on all spans within it. */ - public abstract long traceId(); + /** + * Trace identifier, set on all spans within it. + * + *

Encoded as 16 or 32 lowercase hex characters corresponding to 64 or 128 bits. For example, a + * 128bit trace ID looks like {@code 4e441824ec2b6a44ffdc9bb9a6453df3}. + * + *

Some systems downgrade trace identifiers to 64bit by dropping the left-most 16 characters. + * For example, {@code 4e441824ec2b6a44ffdc9bb9a6453df3} becomes {@code ffdc9bb9a6453df3}. + */ + public abstract String traceId(); - /** The parent's {@link #id} or null if this the root span in a trace. */ - @Nullable public abstract Long parentId(); + /** + * The parent's {@link #id} or null if this the root span in a trace. + * + *

This is the same encoding as {@link #id}. For example {@code ffdc9bb9a6453df3} + */ + @Nullable public abstract String parentId(); /** - * Unique 8-byte identifier of this span within a trace. + * Unique 64bit identifier for this operation within the trace. + * + *

Encoded as 16 lowercase hex characters. For example {@code ffdc9bb9a6453df3} * *

A span is uniquely identified in storage by ({@linkplain #traceId}, {@linkplain #id()}). */ - public abstract long id(); + public abstract String id(); /** Indicates the primary span type. */ public enum Kind { @@ -157,7 +164,13 @@ public enum Kind { // Nullable for data conversion especially late arriving data which might not have an annotation @Nullable public abstract Endpoint localEndpoint(); - /** When an RPC (or messaging) span, indicates the other side of the connection. */ + /** + * When an RPC (or messaging) span, indicates the other side of the connection. + * + *

By recording the remote endpoint, your trace will contain network context even if the peer + * is not tracing. For example,For example, you can record the IP from the {@code X-Forwarded-For} + * header or or the service name and socket of a remote peer. + */ @Nullable public abstract Endpoint remoteEndpoint(); /** @@ -189,53 +202,15 @@ public enum Kind { @Nullable public String localServiceName() { Endpoint localEndpoint = localEndpoint(); - return localEndpoint != null && !"".equals(localEndpoint.serviceName) - ? localEndpoint.serviceName - : null; + return localEndpoint != null ? localEndpoint.serviceName() : null; } @Nullable public String remoteServiceName() { Endpoint remoteEndpoint = remoteEndpoint(); - return remoteEndpoint != null && !"".equals(remoteEndpoint.serviceName) - ? remoteEndpoint.serviceName - : null; - } - - /** Returns the hex representation of the span's trace ID */ - public String traceIdString() { - if (traceIdHigh() != 0) { - char[] result = new char[32]; - writeHexLong(result, 0, traceIdHigh()); - writeHexLong(result, 16, traceId()); - return new String(result); - } - char[] result = new char[16]; - writeHexLong(result, 0, traceId()); - return new String(result); - } - - /** Returns {@code $traceId.$spanId<:$parentId or $spanId} */ - public String idString() { - int resultLength = (3 * 16) + 3; // 3 ids and the constant delimiters - if (traceIdHigh() != 0) resultLength += 16; - char[] result = new char[resultLength]; - int pos = 0; - if (traceIdHigh() != 0) { - writeHexLong(result, pos, traceIdHigh()); - pos += 16; - } - writeHexLong(result, pos, traceId()); - pos += 16; - result[pos++] = '.'; - writeHexLong(result, pos, id()); - pos += 16; - result[pos++] = '<'; - result[pos++] = ':'; - writeHexLong(result, pos, parentId() != null ? parentId() : id()); - return new String(result); + return remoteEndpoint != null ? remoteEndpoint.serviceName() : null; } - public static Builder builder() { + public static Builder newBuilder() { return new Builder(); } @@ -244,10 +219,9 @@ public Builder toBuilder() { } public static final class Builder { - Long traceId; - long traceIdHigh; - Long parentId; - Long id; + String traceId; + String parentId; + String id; Kind kind; String name; Long timestamp; @@ -260,7 +234,6 @@ public static final class Builder { Boolean shared; public Builder clear() { - traceIdHigh = 0L; traceId = null; parentId = null; id = null; @@ -279,7 +252,6 @@ public Builder clear() { @Override public Builder clone() { Builder result = new Builder(); - result.traceIdHigh = traceIdHigh; result.traceId = traceId; result.parentId = parentId; result.id = id; @@ -331,60 +303,38 @@ public Builder clear() { } /** - * Decodes the trace ID from its lower-hex representation. - * - *

Use this instead decoding yourself and calling {@link #traceIdHigh(long)} and {@link - * #traceId(long)} + * @throws IllegalArgumentException if not lower-hex format + * @see Span#id() */ public Builder traceId(String traceId) { - checkNotNull(traceId, "traceId"); - if (traceId.length() == 32) { - traceIdHigh(lowerHexToUnsignedLong(traceId, 0)); - } - return traceId(lowerHexToUnsignedLong(traceId)); - } - - /** @see Span#traceIdHigh */ - public Builder traceIdHigh(long traceIdHigh) { - this.traceIdHigh = traceIdHigh; - return this; - } - - /** @see Span#traceId */ - public Builder traceId(long traceId) { - this.traceId = traceId; + this.traceId = normalizeTraceId(traceId); return this; } /** - * Decodes the parent ID from its lower-hex representation. - * - *

Use this instead decoding yourself and calling {@link #parentId(Long)} + * @throws IllegalArgumentException if not lower-hex format + * @see Span#parentId() */ public Builder parentId(@Nullable String parentId) { - this.parentId = parentId != null ? lowerHexToUnsignedLong(parentId) : null; - return this; - } - - /** @see Span#parentId */ - public Builder parentId(@Nullable Long parentId) { - this.parentId = parentId; + if (parentId != null) { + int length = parentId.length(); + if (length > 16) throw new IllegalArgumentException("parentId.length > 16"); + validateHex(parentId); + this.parentId = length < 16 ? padLeft(parentId, 16) : parentId; + } return this; } /** - * Decodes the span ID from its lower-hex representation. - * - *

Use this instead decoding yourself and calling {@link #id(long)} + * @throws IllegalArgumentException if not lower-hex format + * @see Span#id() */ public Builder id(String id) { - this.id = lowerHexToUnsignedLong(id); - return this; - } - - /** @see Span#id */ - public Builder id(long id) { - this.id = id; + if (id == null) throw new NullPointerException("id == null"); + int length = id.length(); + if (length > 16) throw new IllegalArgumentException("id.length > 16"); + validateHex(id); + this.id = length < 16 ? padLeft(id, 16) : id; return this; } @@ -429,14 +379,16 @@ public Builder remoteEndpoint(@Nullable Endpoint remoteEndpoint) { /** @see Span#annotations */ public Builder addAnnotation(long timestamp, String value) { if (annotations == null) annotations = new ArrayList<>(2); - annotations.add(Annotation.create(timestamp, value, null)); + annotations.add(Annotation.create(timestamp, value)); return this; } /** @see Span#tags */ public Builder putTag(String key, String value) { if (tags == null) tags = new TreeMap<>(); - this.tags.put(checkNotNull(key, "key"), checkNotNull(value, "value")); + if (key == null) throw new NullPointerException("key == null"); + if (value == null) throw new NullPointerException("value of " + key + " == null"); + this.tags.put(key, value); return this; } @@ -454,7 +406,6 @@ public Builder shared(@Nullable Boolean shared) { public Span build() { return new AutoValue_Span( - traceIdHigh, traceId, parentId, id, @@ -476,6 +427,52 @@ public Span build() { } @Override public String toString() { - return new String(Encoder.JSON.encode(this), UTF_8); + return new String(BytesEncoder.JSON.encode(this), UTF_8); + } + + /** + * Returns a valid lower-hex trace ID, padded left as needed to 16 or 32 characters. + * + * @throws IllegalArgumentException if oversized or not lower-hex + */ + public static String normalizeTraceId(String traceId) { + if (traceId == null) throw new NullPointerException("traceId == null"); + int length = traceId.length(); + if (length > 32) throw new IllegalArgumentException("traceId.length > 32"); + validateHex(traceId); + if (length == 32 || length == 16) { + return traceId; + } else if (length < 16) { + return padLeft(traceId, 16); + } else { + return padLeft(traceId, 32); + } + } + + static String padLeft(String id, int desiredLength) { + StringBuilder builder = new StringBuilder(desiredLength); + int offset = desiredLength - id.length(); + + for (int i = 0; i < offset; i++) builder.append('0'); + builder.append(id); + return builder.toString(); + } + + static void validateHex(String id) { + for (int i = 0, length = id.length(); i < length; i++) { + char c = id.charAt(i); + if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { + throw new IllegalArgumentException(id + " should be lower-hex encoded with no prefix"); + } + } + } + + static > List sortedList(@Nullable List in) { + if (in == null || in.isEmpty()) return Collections.emptyList(); + if (in.size() == 1) return Collections.singletonList(in.get(0)); + Object[] array = in.toArray(); + Arrays.sort(array); + List result = Arrays.asList(array); + return Collections.unmodifiableList(result); } } diff --git a/zipkin/src/main/java/zipkin/internal/v2/codec/Decoder.java b/zipkin/src/main/java/zipkin/internal/v2/codec/BytesDecoder.java similarity index 60% rename from zipkin/src/main/java/zipkin/internal/v2/codec/Decoder.java rename to zipkin/src/main/java/zipkin/internal/v2/codec/BytesDecoder.java index 8e7586bb37a..dc09b746648 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/codec/Decoder.java +++ b/zipkin/src/main/java/zipkin/internal/v2/codec/BytesDecoder.java @@ -20,26 +20,33 @@ /** * @param type of the span, usually {@link zipkin.Span} */ -public interface Decoder { - Decoder JSON = new Decoder() { +public interface BytesDecoder { + BytesDecoder JSON = new BytesDecoder() { @Override public Encoding encoding() { return Encoding.JSON; } - @Override public List decodeList(byte[] span) { - return JsonCodec.readList(new Span2JsonAdapters.Span2Reader(), span); + @Override public Span decode(byte[] span) { // ex decode span in dependencies job + return JsonCodec.read(new Span2JsonAdapters.Span2Reader(), span); } - @Override public List> decodeNestedList(byte[] span) { - return JsonCodec.readList(new Span2JsonAdapters.Span2ListReader(), span); + @Override public List decodeList(byte[] spans) { // ex getTrace + return JsonCodec.readList(new Span2JsonAdapters.Span2Reader(), spans); + } + + @Override public List> decodeNestedList(byte[] traces) { // ex getTraces + return JsonCodec.readList(new Span2JsonAdapters.Span2ListReader(), traces); } }; Encoding encoding(); - /** throws {@linkplain IllegalArgumentException} if the spans couldn't be decoded */ - List decodeList(byte[] span); + /** throws {@linkplain IllegalArgumentException} if the span couldn't be decoded */ + S decode(byte[] span); /** throws {@linkplain IllegalArgumentException} if the spans couldn't be decoded */ - List> decodeNestedList(byte[] span); + List decodeList(byte[] spans); + + /** throws {@linkplain IllegalArgumentException} if the traces couldn't be decoded */ + List> decodeNestedList(byte[] traces); } diff --git a/zipkin/src/main/java/zipkin/internal/v2/codec/Encoder.java b/zipkin/src/main/java/zipkin/internal/v2/codec/BytesEncoder.java similarity index 60% rename from zipkin/src/main/java/zipkin/internal/v2/codec/Encoder.java rename to zipkin/src/main/java/zipkin/internal/v2/codec/BytesEncoder.java index 5f0d9c53f2c..d7bed047660 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/codec/Encoder.java +++ b/zipkin/src/main/java/zipkin/internal/v2/codec/BytesEncoder.java @@ -13,6 +13,7 @@ */ package zipkin.internal.v2.codec; +import java.util.List; import zipkin.internal.JsonCodec; import zipkin.internal.v2.Span; @@ -21,8 +22,8 @@ /** * @param type of the span, usually {@link zipkin.Span} */ -public interface Encoder { - Encoder JSON = new Encoder() { +public interface BytesEncoder { + BytesEncoder JSON = new BytesEncoder() { @Override public Encoding encoding() { return Encoding.JSON; } @@ -30,14 +31,24 @@ public interface Encoder { @Override public byte[] encode(Span span) { return JsonCodec.write(SPAN_WRITER, span); } + + @Override public byte[] encodeList(List spans) { + return JsonCodec.writeList(SPAN_WRITER, spans); + } + + @Override public byte[] encodeNestedList(List> spans) { + return JsonCodec.writeNestedList(SPAN_WRITER, spans); + } }; Encoding encoding(); - /** - * Serialize a span recorded from instrumentation into its binary form. - * - * @param span cannot be null - */ + /** Serializes a span recorded from instrumentation into its binary form. */ byte[] encode(S span); + + /** Serializes a list of spans recorded from instrumentation into its binary form. */ + byte[] encodeList(List spans); + + /** Serializes a list of spans recorded from instrumentation into its binary form. */ + byte[] encodeNestedList(List> traces); } diff --git a/zipkin/src/main/java/zipkin/internal/v2/codec/MessageEncoder.java b/zipkin/src/main/java/zipkin/internal/v2/codec/MessageEncoder.java deleted file mode 100644 index 24af2fcbb09..00000000000 --- a/zipkin/src/main/java/zipkin/internal/v2/codec/MessageEncoder.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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.codec; - -import java.util.List; - -/** - * Senders like Kafka use byte[] message encoding. This provides helpers to concatenate spans into a - * list. - */ -public interface MessageEncoder { - MessageEncoder JSON_BYTES = new MessageEncoder() { - @Override public Encoding encoding() { - return Encoding.JSON; - } - - @Override public byte[] encode(List values) { - byte[] buf = new byte[encoding().listSizeInBytes(values)]; - int pos = 0; - buf[pos++] = '['; - for (int i = 0, length = values.size(); i < length; ) { - byte[] v = values.get(i++); - System.arraycopy(v, 0, buf, pos, v.length); - pos += v.length; - if (i < length) buf[pos++] = ','; - } - buf[pos] = ']'; - return buf; - } - }; - - Encoding encoding(); - - /** - * Combines a list of encoded spans into an encoded list. For example, in json, this would be - * comma-separated and enclosed by brackets. - * - *

The primary use of this is batch reporting spans. For example, spans are {@link - * Encoder#encode(Object) encoded} one-by-one into a queue. This queue is drained up to a byte - * threshold. Then, the list is encoded with this function and reported out-of-process. - */ - M encode(List encodedSpans); -} diff --git a/zipkin/src/main/java/zipkin/internal/v2/codec/Span2JsonAdapters.java b/zipkin/src/main/java/zipkin/internal/v2/codec/Span2JsonAdapters.java index 97d397440b1..f11c51c369a 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/codec/Span2JsonAdapters.java +++ b/zipkin/src/main/java/zipkin/internal/v2/codec/Span2JsonAdapters.java @@ -22,15 +22,14 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import zipkin.Annotation; -import zipkin.Endpoint; import zipkin.internal.Buffer; import zipkin.internal.JsonCodec; import zipkin.internal.JsonCodec.JsonReaderAdapter; +import zipkin.internal.v2.Annotation; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import static zipkin.internal.Buffer.asciiSizeInBytes; -import static zipkin.internal.Buffer.ipv6SizeInBytes; import static zipkin.internal.Buffer.jsonEscapedSizeInBytes; /** @@ -44,7 +43,7 @@ static final class Span2Reader implements JsonReaderAdapter { @Override public Span fromJson(JsonReader reader) throws IOException { if (builder == null) { - builder = Span.builder(); + builder = Span.newBuilder(); } else { builder.clear(); } @@ -128,7 +127,7 @@ static final class Span2Reader implements JsonReaderAdapter { } static final JsonReaderAdapter ENDPOINT_READER = reader -> { - Endpoint.Builder result = Endpoint.builder().serviceName(""); + Endpoint.Builder result = Endpoint.newBuilder(); reader.beginObject(); boolean readField = false; while (reader.hasNext()) { @@ -157,55 +156,52 @@ static final class Span2Reader implements JsonReaderAdapter { static final Buffer.Writer ENDPOINT_WRITER = new Buffer.Writer() { @Override public int sizeInBytes(Endpoint value) { - int sizeInBytes = 1; // start curly-brace - if (!value.serviceName.isEmpty()) { - sizeInBytes += "\"serviceName\":\"".length(); - sizeInBytes += jsonEscapedSizeInBytes(value.serviceName) + 1; // for end quote - } - if (value.ipv4 != 0) { - if (sizeInBytes != 1) sizeInBytes++;// comma - sizeInBytes += "\"ipv4\":\"".length(); - sizeInBytes += asciiSizeInBytes(value.ipv4 >> 24 & 0xff) + 1; // for dot - sizeInBytes += asciiSizeInBytes(value.ipv4 >> 16 & 0xff) + 1; // for dot - sizeInBytes += asciiSizeInBytes(value.ipv4 >> 8 & 0xff) + 1; // for dot - sizeInBytes += asciiSizeInBytes(value.ipv4 & 0xff) + 1; // for end quote - } - if (value.ipv6 != null) { - if (sizeInBytes != 1) sizeInBytes++;// comma - sizeInBytes += "\"ipv6\":\"".length() + ipv6SizeInBytes(value.ipv6) + 1; - } - if (value.port != null && value.port != 0) { - if (sizeInBytes != 1) sizeInBytes++;// comma - sizeInBytes += "\"port\":".length() + asciiSizeInBytes(value.port & 0xffff); - } - return ++sizeInBytes;// end curly-brace + 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.isEmpty()) { + if (value.serviceName() != null) { b.writeAscii("\"serviceName\":\""); - b.writeJsonEscaped(value.serviceName).writeByte('"'); + b.writeJsonEscaped(value.serviceName()).writeByte('"'); wroteField = true; } - if (value.ipv4 != 0) { + if (value.ipv4() != null) { if (wroteField) b.writeByte(','); b.writeAscii("\"ipv4\":\""); - b.writeAscii(value.ipv4 >> 24 & 0xff).writeByte('.'); - b.writeAscii(value.ipv4 >> 16 & 0xff).writeByte('.'); - b.writeAscii(value.ipv4 >> 8 & 0xff).writeByte('.'); - b.writeAscii(value.ipv4 & 0xff).writeByte('"'); + b.writeAscii(value.ipv4()).writeByte('"'); wroteField = true; } - if (value.ipv6 != null) { + if (value.ipv6() != null) { if (wroteField) b.writeByte(','); - b.writeAscii("\"ipv6\":\"").writeIpV6(value.ipv6).writeByte('"'); + b.writeAscii("\"ipv6\":\""); + b.writeAscii(value.ipv6()).writeByte('"'); wroteField = true; } - if (value.port != null && value.port != 0) { + if (value.port() != null) { if (wroteField) b.writeByte(','); - b.writeAscii("\"port\":").writeAscii(value.port & 0xffff); + b.writeAscii("\"port\":").writeAscii(value.port()); } b.writeByte('}'); } @@ -213,76 +209,69 @@ static final class Span2Reader implements JsonReaderAdapter { static final Buffer.Writer SPAN_WRITER = new Buffer.Writer() { @Override public int sizeInBytes(Span value) { - int sizeInBytes = 0; - if (value.traceIdHigh() != 0) sizeInBytes += 16; - sizeInBytes += "{\"traceId\":\"".length() + 16 + 1; + int sizeInBytes = 13; // {"traceId":"" + sizeInBytes += value.traceId().length(); if (value.parentId() != null) { - sizeInBytes += ",\"parentId\":\"".length() + 16 + 1; + sizeInBytes += 30; // ,"parentId":"0123456789abcdef" } - sizeInBytes += ",\"id\":\"".length() + 16 + 1; + sizeInBytes += 24; // ,"id":"0123456789abcdef" if (value.kind() != null) { - sizeInBytes += ",\"kind\":\"".length(); - sizeInBytes += value.kind().toString().length() + 1; + sizeInBytes += 10; // ,"kind":"" + sizeInBytes += value.kind().name().length(); } if (value.name() != null) { - sizeInBytes += ",\"name\":\"".length(); - sizeInBytes += jsonEscapedSizeInBytes(value.name()) + 1; + sizeInBytes += 10; // ,"name":"" + sizeInBytes += jsonEscapedSizeInBytes(value.name()); } if (value.timestamp() != null) { - sizeInBytes += ",\"timestamp\":".length(); + sizeInBytes += 13; // ,"timestamp": sizeInBytes += asciiSizeInBytes(value.timestamp()); } if (value.duration() != null) { - sizeInBytes += ",\"duration\":".length(); + sizeInBytes += 12; // ,"duration": sizeInBytes += asciiSizeInBytes(value.duration()); } if (value.localEndpoint() != null) { - sizeInBytes += ",\"localEndpoint\":".length(); + sizeInBytes += 17; // ,"localEndpoint": sizeInBytes += ENDPOINT_WRITER.sizeInBytes(value.localEndpoint()); } if (value.remoteEndpoint() != null) { - sizeInBytes += ",\"remoteEndpoint\":".length(); + sizeInBytes += 18; // ,"remoteEndpoint": sizeInBytes += ENDPOINT_WRITER.sizeInBytes(value.remoteEndpoint()); } if (!value.annotations().isEmpty()) { - sizeInBytes += ",\"annotations\":".length(); + sizeInBytes += 17; // ,"annotations":[] int length = value.annotations().size(); - sizeInBytes += 2; // brackets 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 += ",\"tags\":".length(); - sizeInBytes += 2; // curly braces + 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; // 4 quotes and a colon + sizeInBytes += 5; // "":"" sizeInBytes += jsonEscapedSizeInBytes(entry.getKey()); sizeInBytes += jsonEscapedSizeInBytes(entry.getValue()); } } if (Boolean.TRUE.equals(value.debug())) { - sizeInBytes += ",\"debug\":true".length(); + sizeInBytes += 13; // ,"debug":true } if (Boolean.TRUE.equals(value.shared())) { - sizeInBytes += ",\"shared\":true".length(); + sizeInBytes += 14; // ,"shared":true } - return ++sizeInBytes;// end curly-brace + return ++sizeInBytes; // } } @Override public void write(Span value, Buffer b) { - b.writeAscii("{\"traceId\":\""); - if (value.traceIdHigh() != 0) { - b.writeLowerHex(value.traceIdHigh()); - } - b.writeLowerHex(value.traceId()).writeByte('"'); + b.writeAscii("{\"traceId\":\"").writeAscii(value.traceId()).writeByte('"'); if (value.parentId() != null) { - b.writeAscii(",\"parentId\":\"").writeLowerHex(value.parentId()).writeByte('"'); + b.writeAscii(",\"parentId\":\"").writeAscii(value.parentId()).writeByte('"'); } - b.writeAscii(",\"id\":\"").writeLowerHex(value.id()).writeByte('"'); + b.writeAscii(",\"id\":\"").writeAscii(value.id()).writeByte('"'); if (value.kind() != null) { b.writeAscii(",\"kind\":\"").writeJsonEscaped(value.kind().toString()).writeByte('"'); } @@ -334,15 +323,15 @@ static final class Span2Reader implements JsonReaderAdapter { static final Buffer.Writer ANNOTATION_WRITER = new Buffer.Writer() { @Override public int sizeInBytes(Annotation value) { - int sizeInBytes = 0; - sizeInBytes += "{\"timestamp\":".length() + asciiSizeInBytes(value.timestamp); - sizeInBytes += ",\"value\":\"".length() + jsonEscapedSizeInBytes(value.value) + 1; - return ++sizeInBytes;// end curly-brace + int sizeInBytes = 25; // {"timestamp":,"value":""} + sizeInBytes += asciiSizeInBytes(value.timestamp()); + sizeInBytes += jsonEscapedSizeInBytes(value.value()); + return sizeInBytes; } @Override public void write(Annotation value, Buffer b) { - b.writeAscii("{\"timestamp\":").writeAscii(value.timestamp); - b.writeAscii(",\"value\":\"").writeJsonEscaped(value.value).writeAscii("\"}"); + b.writeAscii("{\"timestamp\":").writeAscii(value.timestamp()); + b.writeAscii(",\"value\":\"").writeJsonEscaped(value.value()).writeAscii("\"}"); } }; diff --git a/zipkin/src/main/java/zipkin/internal/v2/storage/InMemoryStorage.java b/zipkin/src/main/java/zipkin/internal/v2/storage/InMemoryStorage.java index 5c9b343326e..29b215ab20a 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/storage/InMemoryStorage.java +++ b/zipkin/src/main/java/zipkin/internal/v2/storage/InMemoryStorage.java @@ -13,6 +13,7 @@ */ package zipkin.internal.v2.storage; +import com.google.auto.value.AutoValue; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -29,13 +30,9 @@ import java.util.TreeMap; import zipkin.DependencyLink; import zipkin.internal.DependencyLinker; -import zipkin.internal.Pair; import zipkin.internal.v2.Call; import zipkin.internal.v2.Span; -import static zipkin.internal.GroupByTraceId.TRACE_DESCENDING; -import static zipkin.internal.Util.sortedList; - /** * Test storage component that keeps all spans in memory, accepting them on the calling thread. * @@ -100,17 +97,17 @@ public InMemoryStorage build() { * other maps are derived from the span values here. This uses a list for the spans, so that it is * visible (via /api/v2/trace/id?raw) when instrumentation report the same spans multiple times. */ - private final SortedMultimap, Span> spansByTraceIdTimeStamp = - new SortedMultimap(VALUE_2_DESCENDING) { + private final SortedMultimap spansByTraceIdTimeStamp = + new SortedMultimap(TIMESTAMP_DESCENDING) { @Override Collection valueContainer() { return new LinkedList<>(); } }; /** This supports span lookup by {@link Span#traceId lower 64-bits of the trace ID} */ - private final SortedMultimap> traceIdToTraceIdTimeStamps = - new SortedMultimap>(Long::compareTo) { - @Override Collection> valueContainer() { + private final SortedMultimap traceIdToTraceIdTimeStamps = + new SortedMultimap(String::compareTo) { + @Override Collection valueContainer() { return new LinkedHashSet<>(); } }; @@ -147,24 +144,36 @@ public synchronized void clear() { evictToRecoverSpans(spansToRecover); for (Span span : spans) { Long timestamp = span.timestamp() != null ? span.timestamp() : Long.MIN_VALUE; - Pair traceIdTimeStamp = Pair.create(span.traceId(), timestamp); + String lowTraceId = lowTraceId(span.traceId()); + TraceIdTimestamp traceIdTimeStamp = TraceIdTimestamp.create(lowTraceId, timestamp); spansByTraceIdTimeStamp.put(traceIdTimeStamp, span); - traceIdToTraceIdTimeStamps.put(span.traceId(), traceIdTimeStamp); + traceIdToTraceIdTimeStamps.put(lowTraceId, traceIdTimeStamp); acceptedSpanCount++; String spanName = span.name(); if (span.localServiceName() != null) { - serviceToTraceIds.put(span.localServiceName(), span.traceId()); + serviceToTraceIds.put(span.localServiceName(), lowTraceId); serviceToSpanNames.put(span.localServiceName(), spanName); } if (span.remoteServiceName() != null) { - serviceToTraceIds.put(span.remoteServiceName(), span.traceId()); + serviceToTraceIds.put(span.remoteServiceName(), lowTraceId); serviceToSpanNames.put(span.remoteServiceName(), spanName); } } return Call.create(null /* Void == null */); } + @AutoValue + static abstract class TraceIdTimestamp { + static TraceIdTimestamp create(String traceId, long timestamp) { + return new AutoValue_InMemoryStorage_TraceIdTimestamp(traceId, timestamp); + } + + abstract String lowTraceId(); + + abstract long timestamp(); + } + /** Returns the count of spans evicted. */ int evictToRecoverSpans(int spansToRecover) { int spansEvicted = 0; @@ -179,15 +188,15 @@ int evictToRecoverSpans(int spansToRecover) { /** Returns the count of spans evicted. */ private int deleteOldestTrace() { int spansEvicted = 0; - long traceId = spansByTraceIdTimeStamp.delegate.lastKey()._1; - Collection> traceIdTimeStamps = traceIdToTraceIdTimeStamps.remove(traceId); - for (Iterator> traceIdTimeStampIter = traceIdTimeStamps.iterator(); + String lowTraceId = spansByTraceIdTimeStamp.delegate.lastKey().lowTraceId(); + Collection traceIdTimeStamps = traceIdToTraceIdTimeStamps.remove(lowTraceId); + for (Iterator traceIdTimeStampIter = traceIdTimeStamps.iterator(); traceIdTimeStampIter.hasNext(); ) { - Pair traceIdTimeStamp = traceIdTimeStampIter.next(); + TraceIdTimestamp traceIdTimeStamp = traceIdTimeStampIter.next(); Collection spans = spansByTraceIdTimeStamp.remove(traceIdTimeStamp); spansEvicted += spans.size(); } - for (String orphanedService : serviceToTraceIds.removeServiceIfTraceId(traceId)) { + for (String orphanedService : serviceToTraceIds.removeServiceIfTraceId(lowTraceId)) { serviceToSpanNames.remove(orphanedService); } return spansEvicted; @@ -199,13 +208,13 @@ public synchronized Call>> getTraces(QueryRequest request) { } synchronized Call>> getTraces(QueryRequest request, boolean strictTraceId) { - Set traceIdsInTimerange = traceIdsDescendingByTimestamp(request); + Set traceIdsInTimerange = traceIdsDescendingByTimestamp(request); if (traceIdsInTimerange.isEmpty()) return Call.emptyList(); List> result = new ArrayList<>(); - for (Iterator traceId = traceIdsInTimerange.iterator(); - traceId.hasNext() && result.size() < request.limit(); ) { - List next = spansByTraceId(traceId.next()); + for (Iterator lowTraceId = traceIdsInTimerange.iterator(); + lowTraceId.hasNext() && result.size() < request.limit(); ) { + List next = spansByTraceId(lowTraceId.next()); if (!request.test(next)) continue; if (!strictTraceId) { result.add(next); @@ -222,13 +231,13 @@ synchronized Call>> getTraces(QueryRequest request, boolean stri } static Collection> strictByTraceId(List next) { - Map, List> groupedByTraceId = new LinkedHashMap<>(); + Map> groupedByTraceId = new LinkedHashMap<>(); for (Span span : next) { - Pair traceIdPair = Pair.create(span.traceIdHigh(), span.traceId()); - if (!groupedByTraceId.containsKey(traceIdPair)) { - groupedByTraceId.put(traceIdPair, new LinkedList<>()); + String traceId = span.traceId(); + if (!groupedByTraceId.containsKey(traceId)) { + groupedByTraceId.put(traceId, new LinkedList<>()); } - groupedByTraceId.get(traceIdPair).add(span); + groupedByTraceId.get(traceId).add(span); } return groupedByTraceId.values(); } @@ -236,8 +245,8 @@ static Collection> strictByTraceId(List next) { /** Used for testing. Returns all traces unconditionally. */ public synchronized List> getTraces() { List> result = new ArrayList<>(); - for (long traceId : traceIdToTraceIdTimeStamps.keySet()) { - List sameTraceId = spansByTraceId(traceId); + for (String lowTraceId : traceIdToTraceIdTimeStamps.keySet()) { + List sameTraceId = spansByTraceId(lowTraceId); if (strictTraceId) { result.addAll(strictByTraceId(sameTraceId)); } else { @@ -247,8 +256,8 @@ public synchronized List> getTraces() { return result; } - Set traceIdsDescendingByTimestamp(QueryRequest request) { - Collection> traceIdTimestamps = request.serviceName() != null + Set traceIdsDescendingByTimestamp(QueryRequest request) { + Collection traceIdTimestamps = request.serviceName() != null ? traceIdTimestampsByServiceName(request.serviceName()) : spansByTraceIdTimeStamp.keySet(); @@ -256,24 +265,25 @@ Set traceIdsDescendingByTimestamp(QueryRequest request) { long startTs = endTs - request.lookback() * 1000; if (traceIdTimestamps == null || traceIdTimestamps.isEmpty()) return Collections.emptySet(); - Set result = new LinkedHashSet<>(); - for (Pair traceIdTimestamp : traceIdTimestamps) { - if (traceIdTimestamp._2 >= startTs || traceIdTimestamp._2 <= endTs) { - result.add(traceIdTimestamp._1); + Set result = new LinkedHashSet<>(); + for (TraceIdTimestamp traceIdTimestamp : traceIdTimestamps) { + if (traceIdTimestamp.timestamp() >= startTs || traceIdTimestamp.timestamp() <= endTs) { + result.add(traceIdTimestamp.lowTraceId()); } } return result; } - @Override public synchronized Call> getTrace(long traceIdHigh, long traceId) { - List spans = spansByTraceId(traceId); + @Override public synchronized Call> getTrace(String traceId) { + traceId = Span.normalizeTraceId(traceId); + List spans = spansByTraceId(lowTraceId(traceId)); if (spans == null || spans.isEmpty()) return Call.emptyList(); if (!strictTraceId) return Call.create(spans); List filtered = new ArrayList<>(spans); Iterator iterator = filtered.iterator(); while (iterator.hasNext()) { - if (iterator.next().traceIdHigh() != traceIdHigh) { + if (!iterator.next().traceId().equals(traceId)) { iterator.remove(); } } @@ -281,15 +291,13 @@ Set traceIdsDescendingByTimestamp(QueryRequest request) { } @Override public synchronized Call> getServiceNames() { - List result = sortedList(serviceToTraceIds.keySet()); - return Call.create(result); + return Call.create(new ArrayList<>(serviceToTraceIds.keySet())); } @Override public synchronized Call> getSpanNames(String service) { if (service.isEmpty()) return Call.emptyList(); service = service.toLowerCase(Locale.ROOT); // service names are always lowercase! - List result = sortedList(serviceToSpanNames.get(service)); - return Call.create(result); + return Call.create(new ArrayList<>(serviceToSpanNames.get(service))); } @Override @@ -312,27 +320,28 @@ public synchronized Call> getDependencies(long endTs, long }); } - static final Comparator> VALUE_2_DESCENDING = (left, right) -> { - int result = right._2.compareTo(left._2); - if (result != 0) return result; - return right._1.compareTo(left._1); + static final Comparator TIMESTAMP_DESCENDING = (left, right) -> { + long x = left.timestamp(), y = right.timestamp(); + int result = (x < y) ? -1 : ((x == y) ? 0 : 1); + if (result != 0) return -result; // use negative as we are descending + return right.lowTraceId().compareTo(left.lowTraceId()); }; - static final class ServiceNameToTraceIds extends SortedMultimap { + static final class ServiceNameToTraceIds extends SortedMultimap { ServiceNameToTraceIds() { super(String::compareTo); } - @Override Set valueContainer() { + @Override Set valueContainer() { return new LinkedHashSet<>(); } /** Returns service names orphaned by removing the trace ID */ - Set removeServiceIfTraceId(long traceId) { + Set removeServiceIfTraceId(String lowTraceId) { Set result = new LinkedHashSet<>(); - for (Map.Entry> entry : delegate.entrySet()) { - Collection traceIds = entry.getValue(); - if (traceIds.remove(traceId) && traceIds.isEmpty()) { + for (Map.Entry> entry : delegate.entrySet()) { + Collection lowTraceIds = entry.getValue(); + if (lowTraceIds.remove(lowTraceId) && lowTraceIds.isEmpty()) { result.add(entry.getKey()); } } @@ -385,30 +394,24 @@ Collection get(K key) { } } - private List spansByTraceId(long traceId) { + private List spansByTraceId(String lowTraceId) { List sameTraceId = new ArrayList<>(); - for (Pair traceIdTimestamp : traceIdToTraceIdTimeStamps.get(traceId)) { + for (TraceIdTimestamp traceIdTimestamp : traceIdToTraceIdTimeStamps.get(lowTraceId)) { sameTraceId.addAll(spansByTraceIdTimeStamp.get(traceIdTimestamp)); } return sameTraceId; } - private Collection> traceIdTimestampsByServiceName(String serviceName) { - List> traceIdTimestamps = new ArrayList<>(); - for (long traceId : serviceToTraceIds.get(serviceName)) { - traceIdTimestamps.addAll(traceIdToTraceIdTimeStamps.get(traceId)); + private Collection traceIdTimestampsByServiceName(String serviceName) { + List traceIdTimestamps = new ArrayList<>(); + for (String lowTraceId : serviceToTraceIds.get(serviceName)) { + traceIdTimestamps.addAll(traceIdToTraceIdTimeStamps.get(lowTraceId)); } - Collections.sort(traceIdTimestamps, VALUE_2_DESCENDING); + Collections.sort(traceIdTimestamps, TIMESTAMP_DESCENDING); return traceIdTimestamps; } - /** Compares by {@link Span#timestamp()} if present. */ - final static Comparator SPAN_COMPARATOR = new Comparator() { - @Override public int compare(Span left, Span right) { - if (left == right) return 0; - long x = left.timestamp() == null ? Long.MIN_VALUE : left.timestamp(); - long y = right.timestamp() == null ? Long.MIN_VALUE : right.timestamp(); - return x < y ? -1 : x == y ? 0 : 1; - } - }; + static String lowTraceId(String traceId) { + return traceId.length() == 32 ? traceId.substring(16) :traceId; + } } diff --git a/zipkin/src/main/java/zipkin/internal/v2/storage/QueryRequest.java b/zipkin/src/main/java/zipkin/internal/v2/storage/QueryRequest.java index d521b861fc4..9ec190fc36b 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/storage/QueryRequest.java +++ b/zipkin/src/main/java/zipkin/internal/v2/storage/QueryRequest.java @@ -23,8 +23,8 @@ import java.util.Map; import java.util.Set; import javax.annotation.Nullable; -import zipkin.Annotation; -import zipkin.Endpoint; +import zipkin.internal.v2.Annotation; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; /** @@ -237,14 +237,14 @@ timestamp > endTs() * 1000) { for (Span span : spans) { Endpoint localEndpoint = span.localEndpoint(); - String localServiceName = localEndpoint != null ? localEndpoint.serviceName : null; + String localServiceName = span.localServiceName(); if (localServiceName != null) serviceNames.add(localServiceName); if (serviceName() == null || serviceName().equals(localServiceName)) { for (Annotation a : span.annotations()) { - if ("".equals(annotationQueryRemaining.get(a.value))) { - annotationQueryRemaining.remove(a.value); + if ("".equals(annotationQueryRemaining.get(a.value()))) { + annotationQueryRemaining.remove(a.value()); } } for (Map.Entry t : span.tags().entrySet()) { diff --git a/zipkin/src/main/java/zipkin/internal/v2/storage/SpanConsumer.java b/zipkin/src/main/java/zipkin/internal/v2/storage/SpanConsumer.java index 789b245292e..a197ada8bab 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/storage/SpanConsumer.java +++ b/zipkin/src/main/java/zipkin/internal/v2/storage/SpanConsumer.java @@ -16,7 +16,6 @@ import java.util.List; import zipkin.internal.v2.Call; import zipkin.internal.v2.Span; -import zipkin.storage.Callback; // @FunctionalInterface public interface SpanConsumer { diff --git a/zipkin/src/main/java/zipkin/internal/v2/storage/SpanStore.java b/zipkin/src/main/java/zipkin/internal/v2/storage/SpanStore.java index 11b6971a47e..25ef0d73316 100644 --- a/zipkin/src/main/java/zipkin/internal/v2/storage/SpanStore.java +++ b/zipkin/src/main/java/zipkin/internal/v2/storage/SpanStore.java @@ -41,12 +41,13 @@ public interface SpanStore { * found. * *

When {@link StorageComponent.Builder#strictTraceId(boolean)} is true, spans with the same - * {@code traceIdLow} are returned even if the {@code traceIdHigh is different}. + * right-most 16 characters are returned even if the characters to the left are not. * - * @param traceIdHigh The upper 64-bits of the trace ID. See {@link Span#traceIdHigh()} - * @param traceIdLow The lower 64-bits of the trace ID. See {@link Span#traceId()} + *

Implementations should use {@link Span#normalizeTraceId(String)} to ensure consistency. + * + * @param traceId the {@link Span#traceId() trace ID} */ - Call> getTrace(long traceIdHigh, long traceIdLow); + Call> getTrace(String traceId); /** * Retrieves all {@link Span#localEndpoint() local} and {@link Span#remoteEndpoint() remote} diff --git a/zipkin/src/main/java/zipkin/storage/AsyncSpanConsumer.java b/zipkin/src/main/java/zipkin/storage/AsyncSpanConsumer.java index dfb084c1fbf..2b96df71a5d 100644 --- a/zipkin/src/main/java/zipkin/storage/AsyncSpanConsumer.java +++ b/zipkin/src/main/java/zipkin/storage/AsyncSpanConsumer.java @@ -16,7 +16,6 @@ import java.util.List; import zipkin.Codec; import zipkin.Span; -import zipkin.collector.CollectorSampler; /** * Spans are created in instrumentation, transported out-of-band, and eventually persisted. A span diff --git a/zipkin/src/test/java/zipkin/collector/CollectorTest.java b/zipkin/src/test/java/zipkin/collector/CollectorTest.java index 52e396e352f..75021fe57bd 100644 --- a/zipkin/src/test/java/zipkin/collector/CollectorTest.java +++ b/zipkin/src/test/java/zipkin/collector/CollectorTest.java @@ -22,8 +22,7 @@ import zipkin.internal.V2SpanConverter; import zipkin.internal.V2StorageComponent; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Encoder; -import zipkin.internal.v2.codec.MessageEncoder; +import zipkin.internal.v2.codec.BytesEncoder; import zipkin.internal.v2.storage.SpanConsumer; import static java.util.Arrays.asList; @@ -85,7 +84,7 @@ public class CollectorTest { } @Test public void convertsSpan2Format() { - byte[] bytes = MessageEncoder.JSON_BYTES.encode(asList(Encoder.JSON.encode(span2_1))); + byte[] bytes = BytesEncoder.JSON.encodeList(asList(span2_1)); collector.acceptSpans(bytes, SpanDecoder.DETECTING_DECODER, NOOP); verify(collector).acceptSpans(bytes, SpanDecoder.DETECTING_DECODER, NOOP); @@ -107,7 +106,7 @@ abstract class WithSpan2 extends V2StorageComponent implements zipkin.storage.St collector = spy(Collector.builder(Collector.class) .storage(storage).build()); - byte[] bytes = MessageEncoder.JSON_BYTES.encode(asList(Encoder.JSON.encode(span2_1))); + byte[] bytes = BytesEncoder.JSON.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/DependencyLinkerTest.java b/zipkin/src/test/java/zipkin/internal/DependencyLinkerTest.java index 42f06e6ff13..d884577fcba 100644 --- a/zipkin/src/test/java/zipkin/internal/DependencyLinkerTest.java +++ b/zipkin/src/test/java/zipkin/internal/DependencyLinkerTest.java @@ -21,14 +21,15 @@ import javax.annotation.Nullable; import org.junit.Test; import zipkin.DependencyLink; -import zipkin.Endpoint; import zipkin.TestObjects; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import zipkin.internal.v2.Span.Kind; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static zipkin.Constants.ERROR; +import static zipkin.internal.Util.toLowerHex; public class DependencyLinkerTest { List messages = new ArrayList<>(); @@ -467,7 +468,7 @@ public void doesntLinkUnrelatedSpansWhenMissingRootSpan() { @Test public void linksRelatedSpansWhenMissingRootSpan() { - long missingParentId = 1; + long missingParentId = 1L; List trace = asList( span2(1L, missingParentId, 2L, Kind.SERVER, "service1", null, false), span2(1L, 2L, 3L, Kind.SERVER, "service2", null, false) @@ -554,10 +555,13 @@ public void merge_error() { static Span span2(long traceId, @Nullable Long parentId, long id, @Nullable Kind kind, @Nullable String local, @Nullable String remote, boolean isError) { - Span.Builder result = Span.builder(); - result.traceId(traceId).parentId(parentId).id(id).kind(kind); - if (local != null) result.localEndpoint(Endpoint.builder().serviceName(local).build()); - if (remote != null) result.remoteEndpoint(Endpoint.builder().serviceName(remote).build()); + Span.Builder result = Span.newBuilder() + .traceId(toLowerHex(traceId)) + .parentId(parentId != null ? toLowerHex(parentId) : null) + .id(toLowerHex(id)) + .kind(kind); + if (local != null) result.localEndpoint(Endpoint.newBuilder().serviceName(local).build()); + if (remote != null) result.remoteEndpoint(Endpoint.newBuilder().serviceName(remote).build()); if (isError) result.putTag(ERROR, ""); return result.build(); } diff --git a/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java b/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java index 6b402d4fcb3..d247f252bfd 100644 --- a/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java +++ b/zipkin/src/test/java/zipkin/internal/DetectingSpanDecoderTest.java @@ -13,14 +13,11 @@ */ package zipkin.internal; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.junit.Test; import zipkin.Codec; import zipkin.SpanDecoder; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Encoder; -import zipkin.internal.v2.codec.MessageEncoder; +import zipkin.internal.v2.codec.BytesEncoder; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @@ -72,17 +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(Encoder.JSON.encode(span2_1)); + decoder.readSpan(BytesEncoder.JSON.encode(span2_1)); } @Test(expected = IllegalArgumentException.class) public void readSpans_json2_not_list() { - decoder.readSpans(Encoder.JSON.encode(span2_1)); + decoder.readSpans(BytesEncoder.JSON.encode(span2_1)); } @Test public void readSpans_json2() { - byte[] message = MessageEncoder.JSON_BYTES.encode( - Stream.of(span2_1, span2_2).map(Encoder.JSON::encode).collect(Collectors.toList()) - ); + byte[] message = BytesEncoder.JSON.encodeList(asList(span2_1, span2_2)); assertThat(decoder.readSpans(message)) .containsExactly(span1, span2); diff --git a/zipkin/src/test/java/zipkin/internal/NodeTest.java b/zipkin/src/test/java/zipkin/internal/NodeTest.java index 06fa25e97ce..fba3b733c78 100644 --- a/zipkin/src/test/java/zipkin/internal/NodeTest.java +++ b/zipkin/src/test/java/zipkin/internal/NodeTest.java @@ -25,6 +25,7 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static zipkin.internal.Util.toLowerHex; public class NodeTest { List messages = new ArrayList<>(); @@ -97,7 +98,9 @@ public void constructsTraceTree() { Node.TreeBuilder treeBuilder = new Node.TreeBuilder<>(logger, copy.get(0).traceIdString()); for (Span span : copy) { - treeBuilder.addNode(span.parentId, span.id, span); + treeBuilder.addNode( + span.parentId != null ? toLowerHex(span.parentId) : null, toLowerHex(span.id), span + ); } Node root = treeBuilder.build(); assertThat(root.value()) @@ -123,8 +126,9 @@ public void constructTree_noChildLeftBehind() { Node.TreeBuilder treeBuilder = new Node.TreeBuilder<>(logger, spans.get(0).traceIdString()); for (Span span : spans) { - assertThat(treeBuilder.addNode(span.parentId, span.id, span)) - .isTrue(); + assertThat(treeBuilder.addNode( + span.parentId != null ? toLowerHex(span.parentId) : null, toLowerHex(span.id), span + )).isTrue(); } Node tree = treeBuilder.build(); Iterator> iter = tree.traverse(); @@ -146,7 +150,9 @@ public void constructTree_noChildLeftBehind() { Node.TreeBuilder treeBuilder = new Node.TreeBuilder<>(logger, s2.traceIdString()); for (Span span : asList(s2, s3, s4)) { - treeBuilder.addNode(span.parentId, span.id, span); + treeBuilder.addNode( + span.parentId != null ? toLowerHex(span.parentId) : null, toLowerHex(span.id), span + ); } Node root = treeBuilder.build(); assertThat(root.isSyntheticRootForPartialTree()) @@ -164,8 +170,12 @@ public void addNode_skipsOnCycle() { Span s2 = Span.builder().traceId(137L).parentId(2L).id(2L).name("s2").build(); Node.TreeBuilder treeBuilder = new Node.TreeBuilder<>(logger, s2.traceIdString()); - treeBuilder.addNode(s1.parentId, s1.id, s1); - assertThat(treeBuilder.addNode(s2.parentId, s2.id, s2)).isFalse(); + treeBuilder.addNode( + s1.parentId != null ? toLowerHex(s1.parentId) : null, toLowerHex(s1.id), s1 + ); + assertThat(treeBuilder.addNode( + s2.parentId != null ? toLowerHex(s2.parentId) : null, toLowerHex(s2.id), s2 + )).isFalse(); treeBuilder.build(); assertThat(messages).containsExactly( diff --git a/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java b/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java index af7114fcfc5..5cf9c5aba6f 100644 --- a/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java +++ b/zipkin/src/test/java/zipkin/internal/V2JsonSpanDecoderTest.java @@ -16,8 +16,7 @@ import org.junit.Test; import zipkin.SpanDecoder; import zipkin.internal.v2.Span; -import zipkin.internal.v2.codec.Encoder; -import zipkin.internal.v2.codec.MessageEncoder; +import zipkin.internal.v2.codec.BytesEncoder; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; @@ -32,14 +31,11 @@ public class V2JsonSpanDecoderTest { SpanDecoder decoder = new V2JsonSpanDecoder(); @Test(expected = UnsupportedOperationException.class) public void readSpan() { - decoder.readSpan(Encoder.JSON.encode(span2_1)); + decoder.readSpan(BytesEncoder.JSON.encode(span2_1)); } @Test public void readSpans() { - byte[] message = MessageEncoder.JSON_BYTES.encode(asList( - Encoder.JSON.encode(span2_1), - Encoder.JSON.encode(span2_2) - )); + byte[] message = BytesEncoder.JSON.encodeList(asList(span2_1, span2_2)); assertThat(decoder.readSpans(message)) .containsExactly(span1, span2); diff --git a/zipkin/src/test/java/zipkin/internal/V2SpanConverterTest.java b/zipkin/src/test/java/zipkin/internal/V2SpanConverterTest.java index c84fb6e8e7e..7fab665eb78 100644 --- a/zipkin/src/test/java/zipkin/internal/V2SpanConverterTest.java +++ b/zipkin/src/test/java/zipkin/internal/V2SpanConverterTest.java @@ -26,6 +26,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static zipkin.Constants.LOCAL_COMPONENT; +import static zipkin.internal.V2SpanConverter.convert; public class V2SpanConverterTest { Endpoint frontend = Endpoint.create("frontend", 127 << 24 | 1); @@ -37,14 +38,14 @@ public class V2SpanConverterTest { Endpoint kafka = Endpoint.create("kafka", 0); @Test public void client() { - Span simpleClient = Span.builder() + Span simpleClient = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("get") .kind(Kind.CLIENT) - .localEndpoint(frontend) - .remoteEndpoint(backend) + .localEndpoint(convert(frontend)) + .remoteEndpoint(convert(backend)) .timestamp(1472470996199000L) .duration(207000L) .addAnnotation(1472470996238000L, Constants.WIRE_SEND) @@ -77,13 +78,13 @@ public class V2SpanConverterTest { } @Test public void client_unfinished() { - Span simpleClient = Span.builder() + Span simpleClient = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("get") .kind(Kind.CLIENT) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .addAnnotation(1472470996238000L, Constants.WIRE_SEND) .build(); @@ -106,12 +107,12 @@ public class V2SpanConverterTest { } @Test public void client_kindInferredFromAnnotation() { - Span simpleClient = Span.builder() + Span simpleClient = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("get") - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(1472470996238000L - 1472470996199000L) .addAnnotation(1472470996199000L, Constants.CLIENT_SEND) @@ -134,13 +135,13 @@ public class V2SpanConverterTest { } @Test public void noAnnotationsExceptAddresses() { - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("get") - .localEndpoint(frontend) - .remoteEndpoint(backend) + .localEndpoint(convert(frontend)) + .remoteEndpoint(convert(backend)) .timestamp(1472470996199000L) .duration(207000L) .build(); @@ -164,13 +165,13 @@ public class V2SpanConverterTest { } @Test public void fromSpan_redundantAddressAnnotations() { - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .kind(Kind.CLIENT) .name("get") - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(207000L) .build(); @@ -194,13 +195,13 @@ public class V2SpanConverterTest { } @Test public void server() { - Span simpleServer = Span.builder() + Span simpleServer = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .id("216a2aea45d08fc9") .name("get") .kind(Kind.SERVER) - .localEndpoint(backend) - .remoteEndpoint(frontend) + .localEndpoint(convert(backend)) + .remoteEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(207000L) .putTag(TraceKeys.HTTP_PATH, "/api") @@ -229,10 +230,10 @@ public class V2SpanConverterTest { /** Buggy instrumentation can send data with missing endpoints. Make sure we can record it. */ @Test public void missingEndpoints() { - Span span2 = Span.builder() - .traceId(1L) - .parentId(1L) - .id(2L) + Span span2 = Span.newBuilder() + .traceId("1") + .parentId("1") + .id("2") .name("foo") .timestamp(1472470996199000L) .duration(207000L) @@ -254,10 +255,10 @@ public class V2SpanConverterTest { /** No special treatment for invalid core annotations: missing endpoint */ @Test public void missingEndpoints_coreAnnotation() { - Span span2 = Span.builder() - .traceId(1L) - .parentId(1L) - .id(2L) + Span span2 = Span.newBuilder() + .traceId("1") + .parentId("1") + .id("2") .name("foo") .timestamp(1472470996199000L) .addAnnotation(1472470996199000L, "sr") @@ -279,12 +280,12 @@ public class V2SpanConverterTest { } @Test public void localSpan_emptyComponent() { - Span simpleLocal = Span.builder() - .traceId(1L) - .parentId(1L) - .id(2L) + Span simpleLocal = Span.newBuilder() + .traceId("1") + .parentId("1") + .id("2") .name("local") - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(207000L) .build(); @@ -326,7 +327,7 @@ public class V2SpanConverterTest { .addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, backend)) .build(); - Span.Builder builder = Span.builder() + Span.Builder builder = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") @@ -335,8 +336,8 @@ public class V2SpanConverterTest { // the client side owns timestamp and duration Span client = builder.clone() .kind(Kind.CLIENT) - .localEndpoint(frontend) - .remoteEndpoint(backend) + .localEndpoint(convert(frontend)) + .remoteEndpoint(convert(backend)) .timestamp(1472470996199000L) .duration(207000L) .addAnnotation(1472470996238000L, Constants.WIRE_SEND) @@ -349,8 +350,8 @@ public class V2SpanConverterTest { Span server = builder.clone() .kind(Kind.SERVER) .shared(true) - .localEndpoint(backend) - .remoteEndpoint(frontend) + .localEndpoint(convert(backend)) + .remoteEndpoint(convert(frontend)) .timestamp(1472470996250000L) .duration(100000L) .putTag(TraceKeys.HTTP_PATH, "/backend") @@ -376,14 +377,14 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996350000L, Constants.SERVER_SEND, backend)) .build(); - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("get") .kind(Kind.SERVER) .shared(true) - .localEndpoint(backend) + .localEndpoint(convert(backend)) .timestamp(1472470996250000L) .duration(100000L) .build(); @@ -407,7 +408,7 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996406000L, Constants.CLIENT_RECV, frontend)) .build(); - Span.Builder builder = Span.builder() + Span.Builder builder = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") @@ -415,7 +416,7 @@ public class V2SpanConverterTest { Span client = builder.clone() .kind(Kind.CLIENT) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(207000L) .build(); @@ -423,7 +424,7 @@ public class V2SpanConverterTest { Span server = builder.clone() .kind(Kind.SERVER) .shared(true) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996250000L) .duration(100000L) .build(); @@ -443,7 +444,7 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996250000L, Constants.SERVER_RECV, frontend)) .build(); - Span.Builder builder = Span.builder() + Span.Builder builder = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") @@ -451,14 +452,14 @@ public class V2SpanConverterTest { Span client = builder.clone() .kind(Kind.CLIENT) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .build(); Span server = builder.clone() .kind(Kind.SERVER) .shared(true) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996250000L) .build(); @@ -476,13 +477,13 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996199000L, Constants.MESSAGE_SEND, frontend)) .build(); - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("send") .kind(Kind.PRODUCER) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .build(); @@ -502,15 +503,15 @@ public class V2SpanConverterTest { .addBinaryAnnotation(BinaryAnnotation.address(Constants.MESSAGE_ADDR, kafka)) .build(); - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("send") .kind(Kind.PRODUCER) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) - .remoteEndpoint(kafka) + .remoteEndpoint(convert(kafka)) .build(); assertThat(V2SpanConverter.toSpan(span2)) @@ -532,13 +533,13 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996250000L, Constants.WIRE_SEND, frontend)) .build(); - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("send") .kind(Kind.PRODUCER) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(51000L) .build(); @@ -560,13 +561,13 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996199000L, Constants.MESSAGE_RECV, frontend)) .build(); - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("send") .kind(Kind.CONSUMER) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .build(); @@ -588,14 +589,14 @@ public class V2SpanConverterTest { .addBinaryAnnotation(BinaryAnnotation.address(Constants.MESSAGE_ADDR, kafka)) .build(); - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("send") .kind(Kind.CONSUMER) - .localEndpoint(frontend) - .remoteEndpoint(kafka) + .localEndpoint(convert(frontend)) + .remoteEndpoint(convert(kafka)) .timestamp(1472470996199000L) .build(); @@ -618,13 +619,13 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996250000L, Constants.MESSAGE_RECV, frontend)) .build(); - Span span2 = Span.builder() + Span span2 = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("send") .kind(Kind.CONSUMER) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(51000L) .build(); @@ -650,7 +651,7 @@ public class V2SpanConverterTest { .addBinaryAnnotation(BinaryAnnotation.address(Constants.MESSAGE_ADDR, kafka)) .build(); - Span.Builder builder = Span.builder() + Span.Builder builder = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") @@ -658,8 +659,8 @@ public class V2SpanConverterTest { Span producer = builder.clone() .kind(Kind.PRODUCER) - .localEndpoint(frontend) - .remoteEndpoint(kafka) + .localEndpoint(convert(frontend)) + .remoteEndpoint(convert(kafka)) .timestamp(1472470996199000L) .duration(1472470996238000L - 1472470996199000L) .build(); @@ -667,8 +668,8 @@ public class V2SpanConverterTest { Span consumer = builder.clone() .kind(Kind.CONSUMER) .shared(true) - .localEndpoint(backend) - .remoteEndpoint(kafka) + .localEndpoint(convert(backend)) + .remoteEndpoint(convert(kafka)) .timestamp(1472470996403000L) .duration(1472470996406000L - 1472470996403000L) .build(); @@ -691,7 +692,7 @@ public class V2SpanConverterTest { .addAnnotation(Annotation.create(1472470996406000L, Constants.MESSAGE_RECV, frontend)) .build(); - Span.Builder builder = Span.builder() + Span.Builder builder = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") @@ -699,7 +700,7 @@ public class V2SpanConverterTest { Span producer = builder.clone() .kind(Kind.PRODUCER) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996199000L) .duration(1472470996238000L - 1472470996199000L) .build(); @@ -707,7 +708,7 @@ public class V2SpanConverterTest { Span consumer = builder.clone() .kind(Kind.CONSUMER) .shared(true) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp(1472470996403000L) .duration(1472470996406000L - 1472470996403000L) .build(); @@ -731,13 +732,13 @@ public class V2SpanConverterTest { .addBinaryAnnotation(BinaryAnnotation.create("missing", "", null)) .build(); - Span.Builder builder = Span.builder() + Span.Builder builder = Span.newBuilder() .traceId("216a2aea45d08fc9") .id("5b4185666d50f68b") .name("missing"); Span first = builder.clone() - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .addAnnotation(1472470996199000L, "foo") .addAnnotation(1472470996238000L, "bar") .addAnnotation(1472470996403000L, "missing") @@ -746,7 +747,7 @@ public class V2SpanConverterTest { .build(); Span second = builder.clone() - .localEndpoint(backend) + .localEndpoint(convert(backend)) .addAnnotation(1472470996250000L, "baz") .addAnnotation(1472470996350000L, "qux") .putTag("baz", "qux") @@ -776,11 +777,11 @@ public class V2SpanConverterTest { .addBinaryAnnotation(BinaryAnnotation.create("bytes", bytesBuffer, Type.BYTES, frontend)) .build(); - Span span2 = Span.builder() - .traceId(1) + Span span2 = Span.newBuilder() + .traceId("1") .name("test") - .id(2) - .localEndpoint(frontend) + .id("2") + .localEndpoint(convert(frontend)) .putTag("bool", "true") .putTag("short", "20") .putTag("int", "32800") diff --git a/zipkin/src/test/java/zipkin/internal/V2SpanStoreAdapterTest.java b/zipkin/src/test/java/zipkin/internal/V2SpanStoreAdapterTest.java index 2fe6cafb83d..9d9dc1d8fa3 100644 --- a/zipkin/src/test/java/zipkin/internal/V2SpanStoreAdapterTest.java +++ b/zipkin/src/test/java/zipkin/internal/V2SpanStoreAdapterTest.java @@ -41,6 +41,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static zipkin.TestObjects.TODAY; +import static zipkin.internal.V2SpanConverter.convert; public class V2SpanStoreAdapterTest { @Rule public MockitoRule mocks = MockitoJUnit.rule(); @@ -52,7 +53,7 @@ public class V2SpanStoreAdapterTest { Endpoint frontend = Endpoint.create("frontend", 192 << 24 | 168 << 16 | 2); Endpoint backend = Endpoint.create("backend", 192 << 24 | 168 << 16 | 3); - Span.Builder builder = Span.builder() + Span.Builder builder = Span.newBuilder() .traceId("7180c278b62e8f6a5b4185666d50f68b") .id("5b4185666d50f68b") .name("get"); @@ -60,14 +61,14 @@ public class V2SpanStoreAdapterTest { List skewedTrace2 = asList( builder.clone() .kind(Span.Kind.CLIENT) - .localEndpoint(frontend) + .localEndpoint(convert(frontend)) .timestamp((TODAY + 200) * 1000) .duration(120_000L) .build(), builder.clone() .kind(Span.Kind.SERVER) .shared(true) - .localEndpoint(backend) + .localEndpoint(convert(backend)) .timestamp((TODAY + 100) * 1000) // received before sent! .duration(60_000L) .build() @@ -157,7 +158,7 @@ public void getTraces_sync_wrapsIOE() throws IOException { } @Test public void getTrace_sync_callsExecute() throws IOException { - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); when(call.execute()) .thenReturn(Collections.emptyList()); @@ -170,7 +171,7 @@ public void getTraces_sync_wrapsIOE() throws IOException { @Test(expected = UncheckedIOException.class) public void getTrace_sync_wrapsIOE() throws IOException { - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); when(call.execute()) .thenThrow(IOException.class); @@ -179,7 +180,7 @@ public void getTrace_sync_wrapsIOE() throws IOException { } @Test public void getTrace_async_callsEnqueue() { - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); doEnqueue(c -> c.onSuccess(Collections.emptyList())); @@ -190,7 +191,7 @@ public void getTrace_sync_wrapsIOE() throws IOException { @Test public void getTrace_async_doesntWrapIOE() { IOException throwable = new IOException(); - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); doEnqueue(c -> c.onError(throwable)); @@ -200,7 +201,7 @@ public void getTrace_sync_wrapsIOE() throws IOException { } @Test public void getRawTrace_sync_callsExecute() throws IOException { - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); when(call.execute()) .thenReturn(Collections.emptyList()); @@ -213,7 +214,7 @@ public void getTrace_sync_wrapsIOE() throws IOException { @Test(expected = UncheckedIOException.class) public void getRawTrace_sync_wrapsIOE() throws IOException { - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); when(call.execute()) .thenThrow(IOException.class); @@ -222,7 +223,7 @@ public void getRawTrace_sync_wrapsIOE() throws IOException { } @Test public void getRawTrace_async_callsEnqueue() { - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); doEnqueue(c -> c.onSuccess(Collections.emptyList())); @@ -233,7 +234,7 @@ public void getRawTrace_sync_wrapsIOE() throws IOException { @Test public void getRawTrace_async_doesntWrapIOE() { IOException throwable = new IOException(); - when(spanStore.getTrace(3L, 4L)) + when(spanStore.getTrace("00000000000000030000000000000004")) .thenReturn(call); doEnqueue(c -> c.onError(throwable)); @@ -254,6 +255,16 @@ public void getRawTrace_sync_wrapsIOE() throws IOException { verify(call).execute(); } + @Test public void getServiceNames_sortsList() throws IOException { + when(spanStore.getServiceNames()) + .thenReturn(call); + when(call.execute()) + .thenReturn(asList("foo", "bar")); + + assertThat(adapter.getServiceNames()) + .containsExactly("bar", "foo"); + } + @Test(expected = UncheckedIOException.class) public void getServiceNames_sync_wrapsIOE() throws IOException { when(spanStore.getServiceNames()) @@ -297,6 +308,16 @@ public void getServiceNames_sync_wrapsIOE() throws IOException { verify(call).execute(); } + @Test public void getSpanNames_sortsList() throws IOException { + when(spanStore.getSpanNames("service1")) + .thenReturn(call); + when(call.execute()) + .thenReturn(asList("foo", "bar")); + + assertThat(adapter.getSpanNames("service1")) + .containsExactly("bar", "foo"); + } + @Test(expected = UncheckedIOException.class) public void getSpanNames_sync_wrapsIOE() throws IOException { when(spanStore.getSpanNames("service1")) @@ -378,8 +399,8 @@ public void getDependencies_sync_wrapsIOE() throws IOException { @Test public void getTracesMapper_descendingOrder() { assertThat(V2SpanStoreAdapter.getTracesMapper.map(asList( - asList(builder.traceId(1L).timestamp((TODAY + 1) * 1000).build()), - asList(builder.traceId(2L).timestamp((TODAY + 2) * 1000).build()) + asList(builder.traceId("1").timestamp((TODAY + 1) * 1000).build()), + asList(builder.traceId("2").timestamp((TODAY + 2) * 1000).build()) ))).flatExtracting(s -> s) .extracting(s -> s.timestamp) .containsExactly((TODAY + 2) * 1000, (TODAY + 1) * 1000); diff --git a/zipkin/src/test/java/zipkin/internal/v2/AnnotationTest.java b/zipkin/src/test/java/zipkin/internal/v2/AnnotationTest.java new file mode 100644 index 00000000000..27ee22176fb --- /dev/null +++ b/zipkin/src/test/java/zipkin/internal/v2/AnnotationTest.java @@ -0,0 +1,36 @@ +/** + * 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; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AnnotationTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test public void messageWhenMissingValue() { + thrown.expect(NullPointerException.class); + thrown.expectMessage("value"); + + Annotation.create(1L, null); + } + + @Test public void toString_isNice() { + assertThat(Annotation.create(1L, "foo")) + .hasToString("Annotation{timestamp=1, value=foo}"); + } +} diff --git a/zipkin/src/test/java/zipkin/internal/v2/EndpointTest.java b/zipkin/src/test/java/zipkin/internal/v2/EndpointTest.java new file mode 100644 index 00000000000..c6dc098ac49 --- /dev/null +++ b/zipkin/src/test/java/zipkin/internal/v2/EndpointTest.java @@ -0,0 +1,195 @@ +/** + * 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; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.UnknownHostException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EndpointTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test public void missingIpv4IsNull() { + assertThat(Endpoint.newBuilder().build().ipv4()) + .isNull(); + } + + @Test public void newBuilderWithPort_0CoercesToNull() { + assertThat(Endpoint.newBuilder().port(0).build().port()) + .isNull(); + } + + @Test public void newBuilderWithPort_highest() { + assertThat(Endpoint.newBuilder().port(65535).build().port()) + .isEqualTo(65535); + } + + @Test public void ip_addr_ipv4() throws UnknownHostException { + Endpoint.Builder newBuilder = Endpoint.newBuilder(); + assertThat(newBuilder.parseIp(Inet4Address.getByName("1.2.3.4"))).isTrue(); + Endpoint endpoint = newBuilder.build(); + + assertThat(endpoint.ipv4()) + .isEqualTo("1.2.3.4"); + assertThat(endpoint.ipv6()) + .isNull(); + } + + @Test + public void ip_string_ipv4() throws UnknownHostException { + Endpoint.Builder newBuilder = Endpoint.newBuilder(); + assertThat(newBuilder.parseIp("1.2.3.4")).isTrue(); + Endpoint endpoint = newBuilder.build(); + + assertThat(endpoint.ipv4()) + .isEqualTo("1.2.3.4"); + assertThat(endpoint.ipv6()) + .isNull(); + } + + @Test public void ip_ipv6() throws UnknownHostException { + String ipv6 = "2001:db8::c001"; + Endpoint endpoint = Endpoint.newBuilder().ip(ipv6).build(); + + assertThat(endpoint.ipv4()) + .isNull(); + assertThat(endpoint.ipv6()) + .isEqualTo(ipv6); + } + + @Test public void ip_ipv6_addr() throws UnknownHostException { + String ipv6 = "2001:db8::c001"; + Endpoint endpoint = Endpoint.newBuilder().ip(Inet6Address.getByName(ipv6)).build(); + + assertThat(endpoint.ipv4()) + .isNull(); + assertThat(endpoint.ipv6()) + .isEqualTo(ipv6); + } + + @Test public void ip_ipv6_mappedIpv4() throws UnknownHostException { + String ipv6 = "::FFFF:1.2.3.4"; + Endpoint endpoint = Endpoint.newBuilder().ip(ipv6).build(); + + assertThat(endpoint.ipv4()) + .isEqualTo("1.2.3.4"); + assertThat(endpoint.ipv6()) + .isNull(); + } + + @Test public void ip_ipv6_addr_mappedIpv4() throws UnknownHostException { + String ipv6 = "::FFFF:1.2.3.4"; + Endpoint endpoint = Endpoint.newBuilder().ip(Inet6Address.getByName(ipv6)).build(); + + assertThat(endpoint.ipv4()) + .isEqualTo("1.2.3.4"); + assertThat(endpoint.ipv6()) + .isNull(); + } + + @Test public void ip_ipv6_compatIpv4() throws UnknownHostException { + String ipv6 = "::0000:1.2.3.4"; + Endpoint endpoint = Endpoint.newBuilder().ip(ipv6).build(); + + assertThat(endpoint.ipv4()) + .isEqualTo("1.2.3.4"); + assertThat(endpoint.ipv6()) + .isNull(); + } + + @Test public void ip_ipv6_addr_compatIpv4() throws UnknownHostException { + String ipv6 = "::0000:1.2.3.4"; + Endpoint endpoint = Endpoint.newBuilder().ip(Inet6Address.getByName(ipv6)).build(); + + assertThat(endpoint.ipv4()) + .isEqualTo("1.2.3.4"); + assertThat(endpoint.ipv6()) + .isNull(); + } + + @Test public void ipv6_notMappedIpv4() throws UnknownHostException { + String ipv6 = "::ffef:1.2.3.4"; + Endpoint endpoint = Endpoint.newBuilder().ip(ipv6).build(); + + assertThat(endpoint.ipv4()) + .isNull(); + assertThat(endpoint.ipv6()) + .isNull(); + } + + @Test public void ipv6_downcases() throws UnknownHostException { + Endpoint endpoint = Endpoint.newBuilder().ip("2001:DB8::C001").build(); + + assertThat(endpoint.ipv6()) + .isEqualTo("2001:db8::c001"); + } + + @Test public void ip_ipv6_compatIpv4_compressed() throws UnknownHostException { + String ipv6 = "::1.2.3.4"; + Endpoint endpoint = Endpoint.newBuilder().ip(ipv6).build(); + + assertThat(endpoint.ipv4()) + .isEqualTo("1.2.3.4"); + assertThat(endpoint.ipv6()) + .isNull(); + } + + /** This ensures we don't mistake IPv6 localhost for a mapped IPv4 0.0.0.1 */ + @Test public void ipv6_localhost() throws UnknownHostException { + String ipv6 = "::1"; + Endpoint endpoint = Endpoint.newBuilder().ip(ipv6).build(); + + assertThat(endpoint.ipv4()) + .isNull(); + assertThat(endpoint.ipv6()) + .isEqualTo(ipv6); + } + + /** This is an unusable compat Ipv4 of 0.0.0.2. This makes sure it isn't mistaken for localhost */ + @Test public void ipv6_notLocalhost() throws UnknownHostException { + String ipv6 = "::2"; + Endpoint endpoint = Endpoint.newBuilder().ip(ipv6).build(); + + assertThat(endpoint.ipv4()) + .isNull(); + assertThat(endpoint.ipv6()) + .isEqualTo(ipv6); + } + + /** The integer arg of port should be a whole number */ + @Test public void newBuilderWithPort_negativeIsInvalid() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("invalid port -1"); + + assertThat(Endpoint.newBuilder().port(-1).build().port()).isNull(); + } + + /** The integer arg of port should fit in a 16bit unsigned value */ + @Test public void newBuilderWithPort_tooHighIsInvalid() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("invalid port 65536"); + + Endpoint.newBuilder().port(65536).build(); + } + + @Test public void lowercasesServiceName() { + assertThat(Endpoint.newBuilder().serviceName("fFf").ipv4("127.0.0.1").build().serviceName()) + .isEqualTo("fff"); + } +} diff --git a/zipkin/src/test/java/zipkin/internal/v2/SpanTest.java b/zipkin/src/test/java/zipkin/internal/v2/SpanTest.java index af6e6187843..01661ce4109 100644 --- a/zipkin/src/test/java/zipkin/internal/v2/SpanTest.java +++ b/zipkin/src/test/java/zipkin/internal/v2/SpanTest.java @@ -17,65 +17,24 @@ import java.io.ObjectOutputStream; import okio.Buffer; import org.junit.Test; -import zipkin.Annotation; -import zipkin.internal.Util; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.MapEntry.entry; import static zipkin.TestObjects.APP_ENDPOINT; +import static zipkin.internal.V2SpanConverter.convert; public class SpanTest { - Span base = Span.builder().traceId(1L).id(1L).localEndpoint(APP_ENDPOINT).build(); + Span base = Span.newBuilder().traceId("1").id("1").localEndpoint(convert(APP_ENDPOINT)).build(); @Test public void traceIdString() { - Span with128BitId = Span.builder() - .traceId(Util.lowerHexToUnsignedLong("48485a3953bb6124")) - .id(1) + Span with128BitId = base.toBuilder() + .traceId("463ac35c9f6413ad48485a3953bb6124") .name("foo").build(); - assertThat(with128BitId.traceIdString()) - .isEqualTo("48485a3953bb6124"); - } - - @Test public void traceIdString_high() { - Span with128BitId = Span.builder() - .traceId(Util.lowerHexToUnsignedLong("48485a3953bb6124")) - .traceIdHigh(Util.lowerHexToUnsignedLong("463ac35c9f6413ad")) - .id(1) - .name("foo").build(); - - assertThat(with128BitId.traceIdString()) + assertThat(with128BitId.traceId()) .isEqualTo("463ac35c9f6413ad48485a3953bb6124"); } - @Test - public void idString_traceIdHigh() { - Span with128BitId = Span.builder() - .traceId(Util.lowerHexToUnsignedLong("48485a3953bb6124")) - .traceIdHigh(Util.lowerHexToUnsignedLong("463ac35c9f6413ad")) - .id(1) - .name("foo").build(); - - assertThat(with128BitId.idString()) - .isEqualTo("463ac35c9f6413ad48485a3953bb6124.0000000000000001<:0000000000000001"); - } - - @Test - public void idString_withParent() { - Span withParent = Span.builder().name("foo").traceId(1).id(3).parentId(2L).build(); - - assertThat(withParent.idString()) - .isEqualTo("0000000000000001.0000000000000003<:0000000000000002"); - } - - @Test - public void idString_noParent() { - Span noParent = Span.builder().name("foo").traceId(1).id(1).build(); - - assertThat(noParent.idString()) - .isEqualTo("0000000000000001.0000000000000001<:0000000000000001"); - } - @Test public void spanNamesLowercase() { assertThat(base.toBuilder().name("GET").build().name()) .isEqualTo("get"); @@ -89,8 +48,8 @@ public void idString_noParent() { // note: annotations don't also have endpoints, as it is implicit to Span.localEndpoint assertThat(span.annotations()).containsExactly( - Annotation.create(1L, "foo", null), - Annotation.create(2L, "foo", null) + Annotation.create(1L, "foo"), + Annotation.create(2L, "foo") ); } diff --git a/zipkin/src/test/java/zipkin/internal/v2/codec/MessageEncodingTest.java b/zipkin/src/test/java/zipkin/internal/v2/codec/MessageEncodingTest.java deleted file mode 100644 index 151c6521045..00000000000 --- a/zipkin/src/test/java/zipkin/internal/v2/codec/MessageEncodingTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 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.codec; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import org.junit.Test; -import zipkin.internal.Util; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MessageEncodingTest { - - @Test public void emptyList_jsonBytes() throws IOException { - List encoded = Arrays.asList(); - assertThat(MessageEncoder.JSON_BYTES.encode(encoded)) - .isEqualTo("[]".getBytes(Util.UTF_8)); - } - - @Test public void singletonList_jsonBytes() throws IOException { - List encoded = Arrays.asList(new byte[] {'1'}); - assertThat(MessageEncoder.JSON_BYTES.encode(encoded)) - .isEqualTo("[1]".getBytes(Util.UTF_8)); - } - - @Test public void multiItemList_jsonBytes() throws IOException { - List encoded = Arrays.asList(new byte[] {'3'}, new byte[] {'4'}, new byte[] {'5'}); - assertThat(MessageEncoder.JSON_BYTES.encode(encoded)) - .isEqualTo("[3,4,5]".getBytes(Util.UTF_8)); - } -} diff --git a/zipkin/src/test/java/zipkin/internal/v2/codec/SpanJsonAdaptersTest.java b/zipkin/src/test/java/zipkin/internal/v2/codec/SpanJsonAdaptersTest.java index 8e54cfe38a2..31aef0a3c07 100644 --- a/zipkin/src/test/java/zipkin/internal/v2/codec/SpanJsonAdaptersTest.java +++ b/zipkin/src/test/java/zipkin/internal/v2/codec/SpanJsonAdaptersTest.java @@ -14,21 +14,20 @@ package zipkin.internal.v2.codec; import java.io.IOException; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import zipkin.Constants; import zipkin.Endpoint; import zipkin.TraceKeys; -import zipkin.internal.v2.Span; import zipkin.internal.Util; +import zipkin.internal.v2.Span; import static org.assertj.core.api.Assertions.assertThat; import static zipkin.internal.Util.UTF_8; +import static zipkin.internal.V2SpanConverter.convert; public class SpanJsonAdaptersTest { Endpoint frontend = Endpoint.create("frontend", 127 << 24 | 1); @@ -38,14 +37,14 @@ public class SpanJsonAdaptersTest { .port(9000) .build(); - Span span = Span.builder() + Span span = Span.newBuilder() .traceId("7180c278b62e8f6a216a2aea45d08fc9") .parentId("6b221d5bc9e6496c") .id("5b4185666d50f68b") .name("get") .kind(Span.Kind.CLIENT) - .localEndpoint(frontend) - .remoteEndpoint(backend) + .localEndpoint(convert(frontend)) + .remoteEndpoint(convert(backend)) .timestamp(1472470996199000L) .duration(207000L) .addAnnotation(1472470996238000L, Constants.WIRE_SEND) @@ -57,34 +56,34 @@ public class SpanJsonAdaptersTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void spanRoundTrip() throws IOException { - assertThat(Decoder.JSON.decodeList(encodeList(span))) - .containsOnly(span); + assertThat(BytesDecoder.JSON.decode(BytesEncoder.JSON.encode(span))) + .isEqualTo(span); } @Test public void sizeInBytes() throws IOException { assertThat(Span2JsonAdapters.SPAN_WRITER.sizeInBytes(span)) - .isEqualTo(Encoder.JSON.encode(span).length); + .isEqualTo(BytesEncoder.JSON.encode(span).length); } @Test public void spanRoundTrip_64bitTraceId() throws IOException { - span = span.toBuilder().traceIdHigh(0L).build(); + span = span.toBuilder().traceId(span.traceId().substring(16)).build(); - assertThat(Decoder.JSON.decodeList(encodeList(span))) - .containsOnly(span); + assertThat(BytesDecoder.JSON.decode(BytesEncoder.JSON.encode(span))) + .isEqualTo(span); } @Test public void spanRoundTrip_shared() throws IOException { span = span.toBuilder().shared(true).build(); - assertThat(Decoder.JSON.decodeList(encodeList(span))) - .containsOnly(span); + assertThat(BytesDecoder.JSON.decode(BytesEncoder.JSON.encode(span))) + .isEqualTo(span); } @Test public void sizeInBytes_64bitTraceId() throws IOException { - span = span.toBuilder().traceIdHigh(0L).build(); + span = span.toBuilder().traceId(span.traceId().substring(16)).build(); assertThat(Span2JsonAdapters.SPAN_WRITER.sizeInBytes(span)) - .isEqualTo(Encoder.JSON.encode(span).length); + .isEqualTo(BytesEncoder.JSON.encode(span).length); } /** @@ -93,39 +92,37 @@ public class SpanJsonAdaptersTest { */ @Test public void specialCharsInJson() throws IOException { // service name is surrounded by control characters - Span worstSpanInTheWorld = Span.builder().traceId(1L).id(1L) + Span worstSpanInTheWorld = Span.newBuilder().traceId("1").id("1") // name is terrible .name(new String(new char[] {'"', '\\', '\t', '\b', '\n', '\r', '\f'})) - .localEndpoint(Endpoint.create(new String(new char[] {0, 'a', 1}), 0)) // annotation value includes some json newline characters .addAnnotation(1L, "\u2028 and \u2029") // tag key includes a quote and value newlines .putTag("\"foo", "Database error: ORA-00942:\u2028 and \u2029 table or view does not exist\n") .build(); - assertThat(Decoder.JSON.decodeList(encodeList(worstSpanInTheWorld))) - .containsOnly(worstSpanInTheWorld); + assertThat(BytesDecoder.JSON.decode(BytesEncoder.JSON.encode(worstSpanInTheWorld))) + .isEqualTo(worstSpanInTheWorld); } @Test public void niceErrorOnUppercase_traceId() { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage( - "48485A3953BB6124 should be a 1 to 32 character lower-hex string with no prefix"); + thrown.expectMessage("48485A3953BB6124 should be lower-hex encoded with no prefix"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"48485A3953BB6124\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\"\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnEmpty_inputSpans() throws IOException { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Empty input reading List"); - Decoder.JSON.decodeList(new byte[0]); + BytesDecoder.JSON.decodeList(new byte[0]); } /** @@ -135,50 +132,47 @@ public class SpanJsonAdaptersTest { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Malformed reading List from "); - Decoder.JSON.decodeList(new byte[] {'h', 'e', 'l', 'l', 'o'}); + BytesDecoder.JSON.decodeList(new byte[] {'h', 'e', 'l', 'l', 'o'}); } @Test public void spansRoundTrip() throws IOException { List tenClientSpans = Collections.nCopies(10, span); - byte[] message = MessageEncoder.JSON_BYTES.encode( - tenClientSpans.stream().map(Encoder.JSON::encode).collect(Collectors.toList()) - ); + byte[] message = BytesEncoder.JSON.encodeList(tenClientSpans); - assertThat(Decoder.JSON.decodeList(message)) + assertThat(BytesDecoder.JSON.decodeList(message)) .isEqualTo(tenClientSpans); } @Test public void writesTraceIdHighIntoTraceIdField() { - Span with128BitTraceId = Span.builder() - .traceIdHigh(Util.lowerHexToUnsignedLong("48485a3953bb6124")) - .traceId(Util.lowerHexToUnsignedLong("6b221d5bc9e6496c")) - .localEndpoint(frontend) - .id(1).name("").build(); + Span with128BitTraceId = Span.newBuilder() + .traceId("48485a3953bb61246b221d5bc9e6496c") + .localEndpoint(convert(frontend)) + .id("1").name("").build(); - assertThat(new String(Encoder.JSON.encode(with128BitTraceId), Util.UTF_8)) + assertThat(new String(BytesEncoder.JSON.encode(with128BitTraceId), Util.UTF_8)) .startsWith("{\"traceId\":\"48485a3953bb61246b221d5bc9e6496c\""); } @Test public void readsTraceIdHighFromTraceIdField() { - byte[] with128BitTraceId = ("[{\n" + byte[] with128BitTraceId = ("{\n" + " \"traceId\": \"48485a3953bb61246b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\"\n" - + "}]").getBytes(UTF_8); - byte[] withLower64bitsTraceId = ("[{\n" + + "}").getBytes(UTF_8); + byte[] withLower64bitsTraceId = ("{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\"\n" - + "}]").getBytes(UTF_8); + + "}").getBytes(UTF_8); - assertThat(Decoder.JSON.decodeList(with128BitTraceId).get(0)) - .isEqualTo(Decoder.JSON.decodeList(withLower64bitsTraceId).get(0).toBuilder() - .traceIdHigh(Util.lowerHexToUnsignedLong("48485a3953bb6124")).build()); + assertThat(BytesDecoder.JSON.decode(with128BitTraceId)) + .isEqualTo(BytesDecoder.JSON.decode(withLower64bitsTraceId).toBuilder() + .traceId("48485a3953bb61246b221d5bc9e6496c").build()); } @Test public void ignoresNull_topLevelFields() { - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"parentId\": null,\n" + " \"id\": \"6b221d5bc9e6496c\",\n" @@ -191,13 +185,13 @@ public class SpanJsonAdaptersTest { + " \"tags\": null,\n" + " \"debug\": null,\n" + " \"shared\": null\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void ignoresNull_endpoint_topLevelFields() { - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" @@ -207,17 +201,17 @@ public class SpanJsonAdaptersTest { + " \"ipv6\": null,\n" + " \"port\": null\n" + " }\n" - + "}]"; + + "}"; - assertThat(Decoder.JSON.decodeList(json.getBytes(UTF_8)).get(0).localEndpoint()) + assertThat(convert(BytesDecoder.JSON.decode(json.getBytes(UTF_8)).localEndpoint())) .isEqualTo(Endpoint.create("", 127 << 24 | 1)); } @Test public void niceErrorOnIncomplete_endpoint() { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Empty endpoint at $[0].localEndpoint reading List from json"); + thrown.expectMessage("Empty endpoint at $.localEndpoint reading Span from json"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" @@ -227,147 +221,142 @@ public class SpanJsonAdaptersTest { + " \"ipv6\": null,\n" + " \"port\": null\n" + " }\n" - + "}]"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + + "}"; + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnIncomplete_annotation() { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("Incomplete annotation at $[0].annotations[0].timestamp"); + thrown.expectMessage("Incomplete annotation at $.annotations[0].timestamp"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" + " \"annotations\": [\n" + " { \"timestamp\": 1472470996199000}\n" + " ]\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_traceId() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Expected a string but was NULL"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": null,\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\"\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_id() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Expected a string but was NULL"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": null\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_tagValue() { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("No value at $[0].tags.foo"); + thrown.expectMessage("No value at $.tags.foo"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" + " \"tags\": {\n" + " \"foo\": NULL\n" + " }\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_annotationValue() { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("$[0].annotations[0].value"); + thrown.expectMessage("$.annotations[0].value"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" + " \"annotations\": [\n" + " { \"timestamp\": 1472470996199000, \"value\": NULL}\n" + " ]\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void niceErrorOnNull_annotationTimestamp() { thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("$[0].annotations[0].timestamp"); + thrown.expectMessage("$.annotations[0].timestamp"); - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" + " \"annotations\": [\n" + " { \"timestamp\": NULL, \"value\": \"foo\"}\n" + " ]\n" - + "}]"; + + "}"; - Decoder.JSON.decodeList(json.getBytes(UTF_8)); + BytesDecoder.JSON.decode(json.getBytes(UTF_8)); } @Test public void readSpan_localEndpoint_noServiceName() { - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" + " \"localEndpoint\": {\n" + " \"ipv4\": \"127.0.0.1\"\n" + " }\n" - + "}]"; + + "}"; - assertThat(Decoder.JSON.decodeList(json.getBytes(UTF_8)).get(0).localEndpoint()) - .isEqualTo(Endpoint.create("", 127 << 24 | 1)); + assertThat(BytesDecoder.JSON.decode(json.getBytes(UTF_8)).localServiceName()) + .isNull(); } @Test public void readSpan_remoteEndpoint_noServiceName() { - String json = "[{\n" + String json = "{\n" + " \"traceId\": \"6b221d5bc9e6496c\",\n" + " \"name\": \"get-traces\",\n" + " \"id\": \"6b221d5bc9e6496c\",\n" + " \"remoteEndpoint\": {\n" + " \"ipv4\": \"127.0.0.1\"\n" + " }\n" - + "}]"; + + "}"; - assertThat(Decoder.JSON.decodeList(json.getBytes(UTF_8)).get(0).remoteEndpoint()) - .isEqualTo(Endpoint.create("", 127 << 24 | 1)); + assertThat(BytesDecoder.JSON.decode(json.getBytes(UTF_8)).remoteServiceName()) + .isNull(); } @Test public void spanRoundTrip_noRemoteServiceName() throws IOException { - span = span.toBuilder().remoteEndpoint(backend.toBuilder().serviceName("").build()).build(); + span = span.toBuilder() + .remoteEndpoint(convert(backend.toBuilder().serviceName("").build())).build(); - assertThat(Decoder.JSON.decodeList(encodeList(span))) - .containsOnly(span); + assertThat(BytesDecoder.JSON.decode(BytesEncoder.JSON.encode(span))) + .isEqualTo(span); } @Test public void doesntWriteEmptyServiceName() throws IOException { span = span.toBuilder() - .localEndpoint(frontend.toBuilder().serviceName("").build()) + .localEndpoint(convert(frontend.toBuilder().serviceName("").build())) .remoteEndpoint(null).build(); - assertThat(new String(Encoder.JSON.encode(span), UTF_8)) + assertThat(new String(BytesEncoder.JSON.encode(span), UTF_8)) .contains("{\"ipv4\":\"127.0.0.1\"}"); } - - static byte[] encodeList(Span... spans) { - return MessageEncoder.JSON_BYTES.encode( - Arrays.stream(spans).map(Encoder.JSON::encode).collect(Collectors.toList()) - ); - } } diff --git a/zipkin/src/test/java/zipkin/internal/v2/storage/InMemoryStorageTest.java b/zipkin/src/test/java/zipkin/internal/v2/storage/InMemoryStorageTest.java index 03a65d9fb83..deb62161d0f 100644 --- a/zipkin/src/test/java/zipkin/internal/v2/storage/InMemoryStorageTest.java +++ b/zipkin/src/test/java/zipkin/internal/v2/storage/InMemoryStorageTest.java @@ -20,31 +20,34 @@ import java.util.stream.IntStream; import org.junit.Test; import zipkin.DependencyLink; -import zipkin.Endpoint; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -import static zipkin.TestObjects.APP_ENDPOINT; import static zipkin.TestObjects.DAY; import static zipkin.TestObjects.TODAY; +import static zipkin.internal.Util.toLowerHex; public class InMemoryStorageTest { InMemoryStorage storage = InMemoryStorage.newBuilder().build(); @Test public void getTraces_filteringMatchesMostRecentTraces() throws IOException { - List endpoints = IntStream.rangeClosed(1, 10).mapToObj(i -> - Endpoint.create("service" + i, 127 << 24 | i)) + List endpoints = IntStream.rangeClosed(1, 10) + .mapToObj(i -> Endpoint.newBuilder().serviceName("service" + i).ip("127.0.0.1").build()) .collect(Collectors.toList()); long gapBetweenSpans = 100; - List earlySpans = IntStream.rangeClosed(1, 10).mapToObj(i -> Span.builder().name("early") - .traceId(i).id(i).timestamp((TODAY - i) * 1000).duration(1L) - .localEndpoint(endpoints.get(i - 1)).build()).collect(toList()); - - List lateSpans = IntStream.rangeClosed(1, 10).mapToObj(i -> Span.builder().name("late") - .traceId(i + 10).id(i + 10).timestamp((TODAY + gapBetweenSpans - i) * 1000).duration(1L) + List earlySpans = + IntStream.rangeClosed(1, 10).mapToObj(i -> Span.newBuilder().name("early") + .traceId(toLowerHex(i)).id(toLowerHex(i)) + .timestamp((TODAY - i) * 1000).duration(1L) + .localEndpoint(endpoints.get(i - 1)).build()).collect(toList()); + + List lateSpans = IntStream.rangeClosed(1, 10).mapToObj(i -> Span.newBuilder().name("late") + .traceId(toLowerHex(i + 10)).id(toLowerHex(i + 10)) + .timestamp((TODAY + gapBetweenSpans - i) * 1000).duration(1L) .localEndpoint(endpoints.get(i - 1)).build()).collect(toList()); storage.accept(earlySpans).execute(); @@ -77,10 +80,10 @@ public class InMemoryStorageTest { /** It should be safe to run dependency link jobs twice */ @Test public void replayOverwrites() throws IOException { - Span span = Span.builder().traceId(10L).id(10L).name("receive") + Span span = Span.newBuilder().traceId("10").id("10").name("receive") .kind(Span.Kind.CONSUMER) - .localEndpoint(APP_ENDPOINT) - .remoteEndpoint(Endpoint.builder().serviceName("kafka").build()) + .localEndpoint(Endpoint.newBuilder().serviceName("app").build()) + .remoteEndpoint(Endpoint.newBuilder().serviceName("kafka").build()) .timestamp(TODAY * 1000) .build(); diff --git a/zipkin/src/test/java/zipkin/internal/v2/storage/QueryRequestTest.java b/zipkin/src/test/java/zipkin/internal/v2/storage/QueryRequestTest.java index f71ec9f4537..bed7c77edb1 100644 --- a/zipkin/src/test/java/zipkin/internal/v2/storage/QueryRequestTest.java +++ b/zipkin/src/test/java/zipkin/internal/v2/storage/QueryRequestTest.java @@ -19,11 +19,11 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import zipkin.Constants; +import zipkin.internal.v2.Endpoint; import zipkin.internal.v2.Span; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -import static zipkin.TestObjects.APP_ENDPOINT; import static zipkin.TestObjects.DAY; import static zipkin.TestObjects.TODAY; import static zipkin.TraceKeys.HTTP_METHOD; @@ -31,8 +31,8 @@ public class QueryRequestTest { @Rule public ExpectedException thrown = ExpectedException.none(); QueryRequest.Builder queryBuilder = QueryRequest.newBuilder().endTs(TODAY).lookback(60).limit(10); - Span span = Span.builder().traceId(10L).id(10L).name("receive") - .localEndpoint(APP_ENDPOINT) + Span span = Span.newBuilder().traceId("10").id("10").name("receive") + .localEndpoint(Endpoint.newBuilder().serviceName("app").build()) .kind(Span.Kind.CONSUMER) .timestamp(TODAY * 1000) .build(); @@ -146,7 +146,7 @@ public class QueryRequestTest { .build(); assertThat(request.test(asList( - span.toBuilder().id(2).parentId(span.id()).timestamp(null).build(), + span.toBuilder().id("2").parentId(span.id()).timestamp(null).build(), span ))).isTrue(); } @@ -156,8 +156,8 @@ public class QueryRequestTest { .build(); assertThat(request.test(asList( - span.toBuilder().id(2).parentId(span.id()).timestamp(span.timestamp() + DAY * 1000).build(), - span.toBuilder().id(3).parentId(span.id()).build() + span.toBuilder().id("2").parentId(span.id()).timestamp(span.timestamp() + DAY * 1000).build(), + span.toBuilder().id("3").parentId(span.id()).build() ))).isTrue(); } @@ -227,17 +227,17 @@ public class QueryRequestTest { .isFalse(); } - Span foo = span.toBuilder().traceId(1).name("call1").id(1) + Span foo = span.toBuilder().traceId("1").name("call1").id("1") .addAnnotation(span.timestamp(), "foo").build(); // would be foo bar, except lexicographically bar precedes foo - Span barAndFoo = span.toBuilder().traceId(2).name("call2").id(2) + Span barAndFoo = span.toBuilder().traceId("2").name("call2").id("2") .addAnnotation(span.timestamp(), "bar") .addAnnotation(span.timestamp(), "foo").build(); - Span fooAndBazAndQux = span.toBuilder().traceId(3).name("call3").id(3) + Span fooAndBazAndQux = span.toBuilder().traceId("3").name("call3").id("3") .addAnnotation(span.timestamp(), "foo") .putTag("baz", "qux") .build(); - Span barAndFooAndBazAndQux = span.toBuilder().traceId(4).name("call4").id(4) + Span barAndFooAndBazAndQux = span.toBuilder().traceId("4").name("call4").id("4") .addAnnotation(span.timestamp(), "bar") .addAnnotation(span.timestamp(), "foo") .putTag("baz", "qux")