/**
* Copyright 2015-2016 The OpenZipkin Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package zipkin.collector.scribe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import zipkin.Annotation;
import zipkin.BinaryAnnotation;
import zipkin.BinaryAnnotation.Type;
import zipkin.Codec;
import zipkin.Constants;
import zipkin.Endpoint;
import zipkin.Span;
import zipkin.collector.InMemoryCollectorMetrics;
import zipkin.storage.AsyncSpanConsumer;
import zipkin.storage.AsyncSpanStore;
import zipkin.storage.SpanStore;
import zipkin.storage.StorageComponent;
import static com.google.common.base.Charsets.UTF_8;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.core.Is.isA;
import static zipkin.TestObjects.TRACE;
public class ScribeSpanConsumerTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
// scope to scribe as we aren't creating the consumer with the builder.
InMemoryCollectorMetrics scribeMetrics = new InMemoryCollectorMetrics().forTransport("scribe");
List<Span> consumed = new ArrayList<>();
AsyncSpanConsumer consumer = (input, callback) -> {
callback.onSuccess(null);
input.forEach(consumed::add);
};
Span span = TRACE.get(0);
byte[] bytes = Codec.THRIFT.writeSpan(span);
String encodedSpan = new String(Base64.getEncoder().encode(bytes), UTF_8);
@Test
public void entriesWithSpansAreConsumed() throws Exception {
ScribeSpanConsumer scribe = newScribeSpanConsumer("zipkin", consumer);
Scribe.LogEntry entry = new Scribe.LogEntry();
entry.category = "zipkin";
entry.message = encodedSpan;
assertThat(scribe.log(asList(entry)).get())
.isEqualTo(Scribe.ResultCode.OK);
assertThat(consumed)
.containsExactly(span);
assertThat(scribeMetrics.messages()).isEqualTo(1);
assertThat(scribeMetrics.bytes()).isEqualTo(bytes.length);
assertThat(scribeMetrics.spans()).isEqualTo(1);
}
@Test
public void entriesWithoutSpansAreSkipped() throws Exception {
AsyncSpanConsumer consumer = (input, callback) -> {
throw new AssertionError(); // as we shouldn't get here.
};
ScribeSpanConsumer scribe = newScribeSpanConsumer("zipkin", consumer);
Scribe.LogEntry entry = new Scribe.LogEntry();
entry.category = "notzipkin";
entry.message = "hello world";
scribe.log(asList(entry)).get();
assertThat(scribeMetrics.bytes()).isZero();
assertThat(scribeMetrics.spans()).isZero();
}
@Test
public void malformedDataIsDropped() throws Exception {
ScribeSpanConsumer scribe = newScribeSpanConsumer("zipkin", consumer);
Scribe.LogEntry entry = new Scribe.LogEntry();
entry.category = "zipkin";
entry.message = "notbase64";
thrown.expect(ExecutionException.class); // from dereferenced future
thrown.expectCause(isA(IllegalArgumentException.class));
scribe.log(asList(entry)).get();
}
@Test
public void consumerExceptionBeforeCallbackSetsFutureException() throws Exception {
consumer = (input, callback) -> {
throw new NullPointerException();
};
ScribeSpanConsumer scribe = newScribeSpanConsumer("zipkin", consumer);
Scribe.LogEntry entry = new Scribe.LogEntry();
entry.category = "zipkin";
entry.message = encodedSpan;
thrown.expect(ExecutionException.class); // from dereferenced future
thrown.expectMessage("Cannot store spans [f66529c8cc356aa0.f66529c8cc356aa0<:f66529c8cc356aa0] due to NullPointerException()");
scribe.log(asList(entry)).get();
}
/**
* Callbacks are performed asynchronously. If they throw, it hints that we are chaining futures
* when we shouldn't
*/
@Test
public void callbackExceptionDoesntThrow() throws Exception {
consumer = (input, callback) -> {
callback.onError(new NullPointerException());
};
ScribeSpanConsumer scribe = newScribeSpanConsumer("zipkin", consumer);
Scribe.LogEntry entry = new Scribe.LogEntry();
entry.category = "zipkin";
entry.message = encodedSpan;
scribe.log(asList(entry)).get();
assertThat(scribeMetrics.spansDropped()).isEqualTo(1);
}
/** Finagle's zipkin tracer breaks on a column width with a trailing newline */
@Test
public void decodesSpanGeneratedByFinagle() throws Exception {
Scribe.LogEntry entry = new Scribe.LogEntry();
entry.category = "zipkin";
entry.message =
"CgABq/sBMnzE048LAAMAAAAOZ2V0VHJhY2VzQnlJZHMKAATN0p+4EGfTdAoABav7ATJ8xNOPDwAGDAAAAAQKAAEABR/wq+2DeAsAAgAAAAJzcgwAAwgAAX8AAAEGAAIkwwsAAwAAAAx6aXBraW4tcXVlcnkAAAoAAQAFH/Cr7zj4CwACAAAIAGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh\n"
+ "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhDAADCAABfwAAAQYAAiTDCwADAAAADHppcGtpbi1xdWVyeQAACgABAAUf8KwLPyILAAIAAABOR2MoOSwwLlBTU2NhdmVuZ2UsMjAxNS0wOS0xNyAxMjozNzowMiArMDAwMCwzMDQubWlsbGlzZWNvbmRzKzc2Mi5taWNyb3NlY29uZHMpDAADCAABfwAAAQYAAiTDCwADAAAADHppcGtpbi1xdWVyeQAIAAQABKZ6AAoAAQAFH/CsDLfACwACAAAAAnNzDAADCAABfwAAAQYAAiTDCwADAAAADHppcGtpbi1xdWVyeQAADwAIDAAAAAULAAEAAAATc3J2L2ZpbmFnbGUudmVyc2lvbgsAAgAAAAY2LjI4LjAIAAMAAAAGDAAECAABfwAAAQYAAgAACwADAAAADHppcGtpbi1xdWVyeQAACwABAAAAD3Nydi9tdXgvZW5hYmxlZAsAAgAAAAEBCAADAAAAAAwABAgAAX8AAAEGAAIAAAsAAwAAAAx6aXBraW4tcXVlcnkAAAsAAQAAAAJzYQsAAgAAAAEBCAADAAAAAAwABAgAAX8AAAEGAAIkwwsAAwAAAAx6aXBraW4tcXVlcnkAAAsAAQAAAAJjYQsAAgAAAAEBCAADAAAAAAwABAgAAX8AAAEGAAL5YAsAAwAAAAx6aXBraW4tcXVlcnkAAAsAAQAAAAZudW1JZHMLAAIAAAAEAAAAAQgAAwAAAAMMAAQIAAF/AAABBgACJMMLAAMAAAAMemlwa2luLXF1ZXJ5AAACAAkAAA==\n";
newScribeSpanConsumer(entry.category, consumer).log(asList(entry)).get();
char[] as = new char[2048];
Arrays.fill(as, 'a');
String reallyLongAnnotation = new String(as);
Endpoint zipkinQuery =
Endpoint.builder().serviceName("zipkin-query").ipv4(127 << 24 | 1).port(9411).build();
Endpoint zipkinQuery0 = zipkinQuery.toBuilder().port(null).build();
assertThat(consumed).containsExactly(
Span.builder()
.traceId(-6054243957716233329L)
.name("getTracesByIds")
.id(-3615651937927048332L)
.parentId(-6054243957716233329L)
.addAnnotation(Annotation.create(1442493420635000L, Constants.SERVER_RECV, zipkinQuery))
.addAnnotation(Annotation.create(1442493420747000L, reallyLongAnnotation, zipkinQuery))
.addAnnotation(Annotation.create(1442493422583586L,
"Gc(9,0.PSScavenge,2015-09-17 12:37:02 +0000,304.milliseconds+762.microseconds)",
zipkinQuery))
.addAnnotation(Annotation.create(1442493422680000L, Constants.SERVER_SEND, zipkinQuery))
.addBinaryAnnotation(
BinaryAnnotation.create("srv/finagle.version", "6.28.0", zipkinQuery0))
.addBinaryAnnotation(binaryAnnotation("srv/mux/enabled", true, zipkinQuery0))
.addBinaryAnnotation(BinaryAnnotation.address(Constants.SERVER_ADDR, zipkinQuery))
.addBinaryAnnotation(BinaryAnnotation.address(Constants.CLIENT_ADDR,
zipkinQuery.toBuilder().port(63840).build()))
.addBinaryAnnotation(binaryAnnotation("numIds", 1, zipkinQuery))
.debug(false)
.build());
}
static BinaryAnnotation binaryAnnotation(String key, boolean value, Endpoint endpoint) {
return BinaryAnnotation.create(key, value ? new byte[] {1} : new byte[] {0}, Type.BOOL,
endpoint);
}
static BinaryAnnotation binaryAnnotation(String key, int value, Endpoint endpoint) {
byte[] bytes = new byte[] {
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
return BinaryAnnotation.create(key, bytes, Type.I32, endpoint);
}
ScribeSpanConsumer newScribeSpanConsumer(String category, AsyncSpanConsumer consumer) {
return new ScribeSpanConsumer(ScribeCollector.builder()
.category(category)
.metrics(scribeMetrics)
.storage(new StorageComponent() {
@Override public SpanStore spanStore() {
throw new AssertionError();
}
@Override public AsyncSpanStore asyncSpanStore() {
throw new AssertionError();
}
@Override public AsyncSpanConsumer asyncSpanConsumer() {
return consumer;
}
@Override public CheckResult check() {
return CheckResult.OK;
}
@Override public void close() {
throw new AssertionError();
}
}));
}
}