1 /*
2  * Copyright 2018, OpenCensus Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.opencensus.exporter.trace.jaeger;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static io.opencensus.exporter.trace.jaeger.JaegerExporterConfiguration.DEFAULT_DEADLINE;
21 import static java.util.Collections.singletonList;
22 import static java.util.concurrent.TimeUnit.MILLISECONDS;
23 import static org.mockito.ArgumentMatchers.eq;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.verify;
26 
27 import com.google.common.collect.ImmutableMap;
28 import com.google.common.collect.Lists;
29 import io.jaegertracing.internal.exceptions.SenderException;
30 import io.jaegertracing.thrift.internal.senders.HttpSender;
31 import io.jaegertracing.thriftjava.Log;
32 import io.jaegertracing.thriftjava.Process;
33 import io.jaegertracing.thriftjava.Span;
34 import io.jaegertracing.thriftjava.SpanRef;
35 import io.jaegertracing.thriftjava.SpanRefType;
36 import io.jaegertracing.thriftjava.Tag;
37 import io.jaegertracing.thriftjava.TagType;
38 import io.opencensus.common.Timestamp;
39 import io.opencensus.trace.Annotation;
40 import io.opencensus.trace.AttributeValue;
41 import io.opencensus.trace.Link;
42 import io.opencensus.trace.MessageEvent;
43 import io.opencensus.trace.Span.Kind;
44 import io.opencensus.trace.SpanContext;
45 import io.opencensus.trace.SpanId;
46 import io.opencensus.trace.Status;
47 import io.opencensus.trace.TraceId;
48 import io.opencensus.trace.TraceOptions;
49 import io.opencensus.trace.Tracestate;
50 import io.opencensus.trace.export.SpanData;
51 import io.opencensus.trace.export.SpanData.TimedEvent;
52 import java.util.Collections;
53 import java.util.List;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.Captor;
58 import org.mockito.junit.MockitoJUnitRunner;
59 
60 @RunWith(MockitoJUnitRunner.class)
61 public class JaegerExporterHandlerTest {
62   private static final byte FF = (byte) 0xFF;
63 
64   private final HttpSender mockSender = mock(HttpSender.class);
65   private final Process process = new Process("test");
66   private final JaegerExporterHandler handler =
67       new JaegerExporterHandler(mockSender, process, DEFAULT_DEADLINE);
68 
69   @Captor private ArgumentCaptor<List<Span>> captor;
70 
71   @Test
exportShouldConvertFromSpanDataToJaegerThriftSpan()72   public void exportShouldConvertFromSpanDataToJaegerThriftSpan() throws SenderException {
73     final long startTime = 1519629870001L;
74     final long endTime = 1519630148002L;
75     final SpanData spanData =
76         SpanData.create(
77             sampleSpanContext(),
78             SpanId.fromBytes(new byte[] {(byte) 0x7F, FF, FF, FF, FF, FF, FF, FF}),
79             true,
80             "test",
81             Kind.SERVER,
82             Timestamp.fromMillis(startTime),
83             SpanData.Attributes.create(sampleAttributes(), 0),
84             SpanData.TimedEvents.create(singletonList(sampleAnnotation()), 0),
85             SpanData.TimedEvents.create(singletonList(sampleMessageEvent()), 0),
86             SpanData.Links.create(sampleLinks(), 0),
87             0,
88             Status.OK,
89             Timestamp.fromMillis(endTime));
90 
91     handler.export(singletonList(spanData));
92 
93     verify(mockSender).send(eq(process), captor.capture());
94     List<Span> spans = captor.getValue();
95 
96     assertThat(spans.size()).isEqualTo(1);
97     Span span = spans.get(0);
98 
99     assertThat(span.operationName).isEqualTo("test");
100     assertThat(span.spanId).isEqualTo(256L);
101     assertThat(span.traceIdHigh).isEqualTo(-72057594037927936L);
102     assertThat(span.traceIdLow).isEqualTo(1L);
103     assertThat(span.parentSpanId).isEqualTo(Long.MAX_VALUE);
104     assertThat(span.flags).isEqualTo(1);
105     assertThat(span.startTime).isEqualTo(MILLISECONDS.toMicros(startTime));
106     assertThat(span.duration).isEqualTo(MILLISECONDS.toMicros(endTime - startTime));
107 
108     assertThat(span.tags.size()).isEqualTo(5);
109     assertThat(span.tags)
110         .containsExactly(
111             new Tag("BOOL", TagType.BOOL).setVBool(false),
112             new Tag("LONG", TagType.LONG).setVLong(Long.MAX_VALUE),
113             new Tag(JaegerExporterHandler.SPAN_KIND, TagType.STRING).setVStr("server"),
114             new Tag("STRING", TagType.STRING)
115                 .setVStr("Judge of a man by his questions rather than by his answers. -- Voltaire"),
116             new Tag(JaegerExporterHandler.STATUS_CODE, TagType.LONG).setVLong(0));
117 
118     assertThat(span.logs.size()).isEqualTo(2);
119     Log log = span.logs.get(0);
120     assertThat(log.timestamp).isEqualTo(1519629872987654L);
121     assertThat(log.fields.size()).isEqualTo(4);
122     assertThat(log.fields)
123         .containsExactly(
124             new Tag("message", TagType.STRING).setVStr("annotation #1"),
125             new Tag("bool", TagType.BOOL).setVBool(true),
126             new Tag("long", TagType.LONG).setVLong(1337L),
127             new Tag("string", TagType.STRING)
128                 .setVStr("Kind words do not cost much. Yet they accomplish much. -- Pascal"));
129     log = span.logs.get(1);
130     assertThat(log.timestamp).isEqualTo(1519629871123456L);
131     assertThat(log.fields.size()).isEqualTo(4);
132     assertThat(log.fields)
133         .containsExactly(
134             new Tag("message", TagType.STRING).setVStr("sent message"),
135             new Tag("id", TagType.LONG).setVLong(42L),
136             new Tag("compressed_size", TagType.LONG).setVLong(69),
137             new Tag("uncompressed_size", TagType.LONG).setVLong(96));
138 
139     assertThat(span.references.size()).isEqualTo(1);
140     SpanRef reference = span.references.get(0);
141     assertThat(reference.traceIdHigh).isEqualTo(-1L);
142     assertThat(reference.traceIdLow).isEqualTo(-256L);
143     assertThat(reference.spanId).isEqualTo(512L);
144     assertThat(reference.refType).isEqualTo(SpanRefType.CHILD_OF);
145   }
146 
147   @Test
convertErrorSpanDataToJaegerThriftSpan()148   public void convertErrorSpanDataToJaegerThriftSpan() throws SenderException {
149     long startTime = 1519629870001L;
150     long endTime = 1519630148002L;
151     String statusMessage = "timeout";
152     SpanData spanData =
153         SpanData.create(
154             sampleSpanContext(),
155             SpanId.fromBytes(new byte[] {(byte) 0x7F, FF, FF, FF, FF, FF, FF, FF}),
156             true,
157             "test",
158             Kind.SERVER,
159             Timestamp.fromMillis(startTime),
160             SpanData.Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0),
161             SpanData.TimedEvents.create(Collections.<TimedEvent<Annotation>>emptyList(), 0),
162             SpanData.TimedEvents.create(Collections.<TimedEvent<MessageEvent>>emptyList(), 0),
163             SpanData.Links.create(Collections.<Link>emptyList(), 0),
164             0,
165             Status.DEADLINE_EXCEEDED.withDescription(statusMessage),
166             Timestamp.fromMillis(endTime));
167 
168     handler.export(singletonList(spanData));
169 
170     verify(mockSender).send(eq(process), captor.capture());
171     List<Span> spans = captor.getValue();
172 
173     assertThat(spans.size()).isEqualTo(1);
174     Span span = spans.get(0);
175 
176     assertThat(span.tags.size()).isEqualTo(3);
177     assertThat(span.tags)
178         .containsExactly(
179             new Tag(JaegerExporterHandler.SPAN_KIND, TagType.STRING).setVStr("server"),
180             new Tag(JaegerExporterHandler.STATUS_CODE, TagType.LONG).setVLong(4),
181             new Tag(JaegerExporterHandler.STATUS_MESSAGE, TagType.STRING).setVStr(statusMessage));
182   }
183 
sampleSpanContext()184   private static SpanContext sampleSpanContext() {
185     return SpanContext.create(
186         TraceId.fromBytes(new byte[] {FF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}),
187         SpanId.fromBytes(new byte[] {0, 0, 0, 0, 0, 0, 1, 0}),
188         TraceOptions.builder().setIsSampled(true).build(),
189         Tracestate.builder().build());
190   }
191 
sampleAttributes()192   private static ImmutableMap<String, AttributeValue> sampleAttributes() {
193     return ImmutableMap.of(
194         "BOOL", AttributeValue.booleanAttributeValue(false),
195         "LONG", AttributeValue.longAttributeValue(Long.MAX_VALUE),
196         "STRING",
197             AttributeValue.stringAttributeValue(
198                 "Judge of a man by his questions rather than by his answers. -- Voltaire"));
199   }
200 
sampleAnnotation()201   private static SpanData.TimedEvent<Annotation> sampleAnnotation() {
202     return SpanData.TimedEvent.create(
203         Timestamp.create(1519629872L, 987654321),
204         Annotation.fromDescriptionAndAttributes(
205             "annotation #1",
206             ImmutableMap.of(
207                 "bool", AttributeValue.booleanAttributeValue(true),
208                 "long", AttributeValue.longAttributeValue(1337L),
209                 "string",
210                     AttributeValue.stringAttributeValue(
211                         "Kind words do not cost much. Yet they accomplish much. -- Pascal"))));
212   }
213 
sampleMessageEvent()214   private static SpanData.TimedEvent<MessageEvent> sampleMessageEvent() {
215     return SpanData.TimedEvent.create(
216         Timestamp.create(1519629871L, 123456789),
217         MessageEvent.builder(MessageEvent.Type.SENT, 42L)
218             .setCompressedMessageSize(69)
219             .setUncompressedMessageSize(96)
220             .build());
221   }
222 
sampleLinks()223   private static List<Link> sampleLinks() {
224     return Lists.newArrayList(
225         Link.fromSpanContext(
226             SpanContext.create(
227                 TraceId.fromBytes(
228                     new byte[] {FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, FF, 0}),
229                 SpanId.fromBytes(new byte[] {0, 0, 0, 0, 0, 0, 2, 0}),
230                 TraceOptions.builder().setIsSampled(false).build(),
231                 Tracestate.builder().build()),
232             Link.Type.CHILD_LINKED_SPAN,
233             ImmutableMap.of(
234                 "Bool", AttributeValue.booleanAttributeValue(true),
235                 "Long", AttributeValue.longAttributeValue(299792458L),
236                 "String",
237                     AttributeValue.stringAttributeValue(
238                         "Man is condemned to be free; because once thrown into the world, "
239                             + "he is responsible for everything he does. -- Sartre"))));
240   }
241 }
242