package com.github.kristofa.brave.local; import com.github.kristofa.brave.SpanCollectorMetricsHandler; import com.github.kristofa.brave.SpanId; import com.github.kristofa.brave.internal.InternalSpan; import com.twitter.zipkin.gen.Span; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import zipkin.storage.AsyncSpanConsumer; import zipkin.storage.AsyncSpanStore; import zipkin.storage.InMemoryStorage; import zipkin.storage.SpanStore; import zipkin.storage.StorageComponent; import static org.assertj.core.api.Assertions.assertThat; public class LocalSpanCollectorTest { static { InternalSpan.initializeInstanceForTests(); } public final InMemoryStorage storage = new InMemoryStorage(); TestMetricsHander metrics = new TestMetricsHander(); // set flush interval to 0 so that tests can drive flushing explicitly LocalSpanCollector.Config config = LocalSpanCollector.Config.builder().flushInterval(0).build(); @Test public void collectDoesntDoIO() throws Exception { LocalSpanCollector collector = newLocalSpanCollector((spans, callback) -> { throw new AssertionError("spans should only report on flush!"); }); collector.collect(span(1L)); assertThat(storage.spanStore().traceIds()).isEmpty(); } @Test public void collectIncrementsAcceptedMetrics() throws Exception { LocalSpanCollector collector = newLocalSpanCollector((spans, callback) -> { }); collector.collect(span(1L)); assertThat(metrics.acceptedSpans.get()).isEqualTo(1); assertThat(metrics.droppedSpans.get()).isZero(); } @Test public void dropsWhenQueueIsFull() throws Exception { LocalSpanCollector collector = newLocalSpanCollector((spans, callback) -> { }); for (int i = 0; i < 1001; i++) collector.collect(span(1L)); collector.flush(); // manually flush the spans assertThat(metrics.acceptedSpans.get()).isEqualTo(1001); assertThat(metrics.droppedSpans.get()).isEqualTo(1); } @Test public void bundlesSpansIntoOneMessage() throws Exception { AtomicInteger spanCount = new AtomicInteger(); AtomicInteger messageCount = new AtomicInteger(); LocalSpanCollector collector = newLocalSpanCollector((spans, callback) -> { spanCount.addAndGet(spans.size()); messageCount.incrementAndGet(); }); collector.collect(span(1L)); collector.collect(span(2L)); collector.flush(); // manually flush the spans assertThat(spanCount.get()).isEqualTo(2); assertThat(messageCount.get()).isEqualTo(1); } @Test public void incrementsDroppedSpans_exceptionOnCallingThread() throws Exception { LocalSpanCollector collector = newLocalSpanCollector((spans, callback) -> { throw new RuntimeException("couldn't store"); }); collector.collect(span(1L)); collector.collect(span(2L)); collector.flush(); // manually flush the spans assertThat(metrics.droppedSpans.get()).isEqualTo(2); } @Test public void incrementsDroppedSpans_exceptionOnCallbackThread() throws Exception { LocalSpanCollector collector = newLocalSpanCollector((spans, callback) -> callback.onError(new RuntimeException("couldn't store"))); collector.collect(span(1L)); collector.collect(span(2L)); collector.flush(); // manually flush the spans assertThat(metrics.droppedSpans.get()).isEqualTo(2); } class TestMetricsHander implements SpanCollectorMetricsHandler { final AtomicInteger acceptedSpans = new AtomicInteger(); final AtomicInteger droppedSpans = new AtomicInteger(); @Override public void incrementAcceptedSpans(int quantity) { acceptedSpans.addAndGet(quantity); } @Override public void incrementDroppedSpans(int quantity) { droppedSpans.addAndGet(quantity); } } static Span span(long traceId) { return InternalSpan.instance.toSpan(SpanId.builder().spanId(traceId).build()); } static zipkin.Span zipkinSpan(long traceId) { return zipkin.Span.builder().traceId(traceId).id(traceId).name("").build(); } LocalSpanCollector newLocalSpanCollector(AsyncSpanConsumer consumer) { return new LocalSpanCollector(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(); } }, config, metrics); } }