package com.github.kristofa.brave;
import com.twitter.zipkin.gen.Endpoint;
import com.twitter.zipkin.gen.Span;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class LocalTracerTest {
private static final long START_TIME_MICROSECONDS = System.currentTimeMillis() * 1000;
private static final long TRACE_ID = 105;
private static final SpanId PARENT_CONTEXT = SpanId.builder().traceId(TRACE_ID).spanId(103).build();
private static final String COMPONENT_NAME = "componentname";
private static final String OPERATION_NAME = "operationname";
private static final Endpoint ENDPOINT = Endpoint.create("service", 80);
static final zipkin.Endpoint ZIPKIN_ENDPOINT = zipkin.Endpoint.create("service", 80);
long timestamp = START_TIME_MICROSECONDS;
AnnotationSubmitter.Clock clock = () -> timestamp;
private Span span = Brave.toSpan(SpanId.builder().spanId(TRACE_ID).sampled(true).build());
List<zipkin.Span> spans = new ArrayList<>();
Brave brave = newBrave();
Recorder recorder = brave.localTracer().recorder();
@Before
public void setup() {
ThreadLocalServerClientAndLocalSpanState.clear();
}
Brave newBrave() {
return new Brave.Builder(ENDPOINT).clock(clock).reporter(spans::add).build();
}
Brave newBrave(ServerClientAndLocalSpanState state) {
return new Brave.Builder(state).clock(clock).reporter(spans::add).build();
}
/**
* When a local span is started without a timestamp, microseconds and a tick are recorded for
* duration calculation. A binary annotation is added for search by component.
* <p>
* <p/>Ex.
* <pre>
* localTracer.startNewSpan(component, operation); // internally time and nanos are recorded
* </pre>
*/
@Test
public void startNewSpan() {
brave.serverTracer().setStateCurrentTrace(PARENT_CONTEXT, "name");
SpanId newContext = brave.localTracer().startNewSpan(COMPONENT_NAME, OPERATION_NAME);
assertThat(newContext).isEqualTo(
PARENT_CONTEXT.toBuilder()
.parentId(PARENT_CONTEXT.spanId)
.spanId(newContext.spanId)
.build()
);
assertThat(spans).isEmpty(); // doesn't flush on start
recorder.flush(brave.localSpanThreadBinder().get());
zipkin.Span started = spans.get(0);
assertThat(started.timestamp).isEqualTo(START_TIME_MICROSECONDS);
assertThat(started.name).isEqualTo(OPERATION_NAME);
assertThat(started.binaryAnnotations).containsExactly(
zipkin.BinaryAnnotation.create("lc", COMPONENT_NAME, ZIPKIN_ENDPOINT)
);
}
/**
* When a span is started with a timestamp, we can't use nanotime for duration as we don't
* know the nanotime value for that timestamp.
* <p>
* <p/>Ex.
* <pre>
* localTracer.startNewSpan(component, operation, startTime);
*
* </pre>
*/
@Test
public void startSpan_userSuppliedTimestamp() {
brave.serverTracer().setStateCurrentTrace(PARENT_CONTEXT, "name");
brave.localTracer().startNewSpan(COMPONENT_NAME, OPERATION_NAME, 1000L);
recorder.flush(brave.localSpanThreadBinder().get());
assertThat(spans.get(0).timestamp).isEqualTo(1000L);
}
@Test
public void startSpan_unsampled() {
brave = new Brave.Builder().traceSampler(Sampler.NEVER_SAMPLE).build();
assertNull(brave.localTracer().startNewSpan(COMPONENT_NAME, OPERATION_NAME));
}
/**
* <p>
* <p/>Ex.
* <pre>
* localTracer.startSpan(component, operation); // internally nanos is recorded with system time.
* ...
* localTracer.finishSpan(); // above nanos is used to make a precise duration
* </pre>
*/
@Test
public void finishSpan() {
recorder.start(span, START_TIME_MICROSECONDS);
brave.localSpanThreadBinder().setCurrentSpan(span);
timestamp = START_TIME_MICROSECONDS + 500;
brave.localTracer().finishSpan();
assertThat(spans.get(0).duration).isEqualTo(500L);
}
/** Duration of less than one microsecond is confusing to plot and could coerce to null. */
@Test
public void finishSpan_lessThanMicrosRoundUp() {
recorder.start(span, START_TIME_MICROSECONDS);
brave.localSpanThreadBinder().setCurrentSpan(span);
timestamp = START_TIME_MICROSECONDS; // no time passed!
brave.localTracer().finishSpan();
assertThat(spans.get(0).duration).isEqualTo(1L);
}
@Test
public void startSpan_with_inheritable_nested_local_spans() {
InheritableServerClientAndLocalSpanState state =
new InheritableServerClientAndLocalSpanState(Endpoint.create("test-service", 127 << 24 | 1));
brave = newBrave(state);
recorder = brave.localTracer().recorder();
LocalTracer localTracer = brave.localTracer();
assertNull(localTracer.maybeParent());
brave.serverTracer().setStateCurrentTrace(PARENT_CONTEXT, "name");
SpanId span1 = localTracer.startNewSpan(COMPONENT_NAME, OPERATION_NAME);
assertEquals(PARENT_CONTEXT.toBuilder().spanId(span1.spanId).parentId(PARENT_CONTEXT.spanId).build(), span1);
assertEquals(span1.spanId, localTracer.maybeParent().spanId);
SpanId span2 = localTracer.startNewSpan(COMPONENT_NAME, OPERATION_NAME);
assertEquals(PARENT_CONTEXT.toBuilder().spanId(span2.spanId).parentId(span1.spanId).build(), span2);
assertEquals(span2.spanId, localTracer.maybeParent().spanId);
assertEquals(span2.spanId, state.getCurrentLocalSpan().getId());
localTracer.finishSpan();
assertEquals(span1.spanId, state.getCurrentLocalSpan().getId());
localTracer.finishSpan();
assertNull(state.getCurrentLocalSpan());
}
}