| /* |
| * Copyright 2018, OpenCensus 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 io.opencensus.exporter.trace.stackdriver; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static io.opencensus.exporter.trace.stackdriver.StackdriverTraceConfiguration.DEFAULT_DEADLINE; |
| import static io.opencensus.exporter.trace.stackdriver.StackdriverV2ExporterHandler.createResourceLabelKey; |
| import static io.opencensus.exporter.trace.stackdriver.StackdriverV2ExporterHandler.toStringAttributeValueProto; |
| |
| import com.google.auth.Credentials; |
| import com.google.auth.oauth2.AccessToken; |
| import com.google.auth.oauth2.GoogleCredentials; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.cloudtrace.v2.AttributeValue; |
| import com.google.devtools.cloudtrace.v2.Span; |
| import com.google.devtools.cloudtrace.v2.Span.TimeEvent; |
| import com.google.devtools.cloudtrace.v2.Span.TimeEvent.MessageEvent; |
| import com.google.devtools.cloudtrace.v2.StackTrace; |
| import com.google.devtools.cloudtrace.v2.TruncatableString; |
| import com.google.protobuf.Int32Value; |
| import io.opencensus.common.Timestamp; |
| import io.opencensus.contrib.resource.util.CloudResource; |
| import io.opencensus.contrib.resource.util.HostResource; |
| import io.opencensus.resource.Resource; |
| import io.opencensus.trace.Annotation; |
| import io.opencensus.trace.Link; |
| import io.opencensus.trace.Span.Kind; |
| import io.opencensus.trace.SpanContext; |
| import io.opencensus.trace.SpanId; |
| import io.opencensus.trace.Status; |
| import io.opencensus.trace.TraceId; |
| import io.opencensus.trace.TraceOptions; |
| import io.opencensus.trace.Tracestate; |
| import io.opencensus.trace.export.SpanData; |
| import io.opencensus.trace.export.SpanData.TimedEvent; |
| import io.opencensus.trace.export.SpanData.TimedEvents; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Unit tests for proto conversions in {@link StackdriverV2ExporterHandler}. */ |
| @RunWith(JUnit4.class) |
| public final class StackdriverV2ExporterHandlerProtoTest { |
| |
| private static final Credentials FAKE_CREDENTIALS = |
| GoogleCredentials.newBuilder().setAccessToken(new AccessToken("fake", new Date(100))).build(); |
| // OpenCensus constants |
| private static final Timestamp startTimestamp = Timestamp.create(123, 456); |
| private static final Timestamp eventTimestamp1 = Timestamp.create(123, 457); |
| private static final Timestamp eventTimestamp2 = Timestamp.create(123, 458); |
| private static final Timestamp eventTimestamp3 = Timestamp.create(123, 459); |
| private static final Timestamp endTimestamp = Timestamp.create(123, 460); |
| |
| private static final String PROJECT_ID = "PROJECT_ID"; |
| private static final String TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736"; |
| private static final String SPAN_ID = "24aa0b2d371f48c9"; |
| private static final String PARENT_SPAN_ID = "71da8d631536f5f1"; |
| private static final String SPAN_NAME = "MySpanName"; |
| private static final String SD_SPAN_NAME = |
| String.format("projects/%s/traces/%s/spans/%s", PROJECT_ID, TRACE_ID, SPAN_ID); |
| private static final String ANNOTATION_TEXT = "MyAnnotationText"; |
| private static final String ATTRIBUTE_KEY_1 = "MyAttributeKey1"; |
| private static final String ATTRIBUTE_KEY_2 = "MyAttributeKey2"; |
| |
| private static final int DROPPED_ATTRIBUTES_COUNT = 1; |
| private static final int DROPPED_ANNOTATIONS_COUNT = 2; |
| private static final int DROPPED_NETWORKEVENTS_COUNT = 3; |
| private static final int DROPPED_LINKS_COUNT = 4; |
| private static final int CHILD_SPAN_COUNT = 13; |
| |
| private static final Annotation annotation = Annotation.fromDescription(ANNOTATION_TEXT); |
| private static final io.opencensus.trace.MessageEvent recvMessageEvent = |
| io.opencensus.trace.MessageEvent.builder(io.opencensus.trace.MessageEvent.Type.RECEIVED, 1) |
| .build(); |
| private static final io.opencensus.trace.MessageEvent sentMessageEvent = |
| io.opencensus.trace.MessageEvent.builder(io.opencensus.trace.MessageEvent.Type.SENT, 1) |
| .build(); |
| private static final Status status = Status.DEADLINE_EXCEEDED.withDescription("TooSlow"); |
| private static final SpanId parentSpanId = SpanId.fromLowerBase16(PARENT_SPAN_ID); |
| private static final SpanId spanId = SpanId.fromLowerBase16(SPAN_ID); |
| private static final TraceId traceId = TraceId.fromLowerBase16(TRACE_ID); |
| private static final TraceOptions traceOptions = TraceOptions.DEFAULT; |
| private static final SpanContext spanContext = |
| SpanContext.create(traceId, spanId, traceOptions, Tracestate.builder().build()); |
| |
| private static final List<TimedEvent<Annotation>> annotationsList = |
| ImmutableList.of( |
| SpanData.TimedEvent.create(eventTimestamp1, annotation), |
| SpanData.TimedEvent.create(eventTimestamp3, annotation)); |
| private static final List<TimedEvent<io.opencensus.trace.MessageEvent>> networkEventsList = |
| ImmutableList.of( |
| SpanData.TimedEvent.create(eventTimestamp1, recvMessageEvent), |
| SpanData.TimedEvent.create(eventTimestamp2, sentMessageEvent)); |
| private static final List<Link> linksList = |
| ImmutableList.of(Link.fromSpanContext(spanContext, Link.Type.CHILD_LINKED_SPAN)); |
| |
| private static final SpanData.Attributes attributes = |
| SpanData.Attributes.create( |
| ImmutableMap.of( |
| ATTRIBUTE_KEY_1, |
| io.opencensus.trace.AttributeValue.longAttributeValue(10L), |
| ATTRIBUTE_KEY_2, |
| io.opencensus.trace.AttributeValue.booleanAttributeValue(true)), |
| DROPPED_ATTRIBUTES_COUNT); |
| private static final TimedEvents<Annotation> annotations = |
| TimedEvents.create(annotationsList, DROPPED_ANNOTATIONS_COUNT); |
| private static final TimedEvents<io.opencensus.trace.MessageEvent> messageEvents = |
| TimedEvents.create(networkEventsList, DROPPED_NETWORKEVENTS_COUNT); |
| private static final SpanData.Links links = SpanData.Links.create(linksList, DROPPED_LINKS_COUNT); |
| private static final Map<String, AttributeValue> EMPTY_RESOURCE_LABELS = Collections.emptyMap(); |
| private static final Resource CUSTOM_RESOURCE = |
| Resource.create( |
| "MyOwnResouce", |
| ImmutableMap.of( |
| CloudResource.ACCOUNT_ID_KEY, |
| "my-project", |
| CloudResource.ZONE_KEY, |
| "us-east1", |
| HostResource.ID_KEY, |
| "my-instance")); |
| private static final ImmutableMap<String, AttributeValue> EXPECTED_RESOURCE_ATTRIBUTES = |
| ImmutableMap.of( |
| createResourceLabelKey(CloudResource.ACCOUNT_ID_KEY), |
| toStringAttributeValueProto("my-project"), |
| createResourceLabelKey(CloudResource.ZONE_KEY), |
| toStringAttributeValueProto("us-east1"), |
| createResourceLabelKey(HostResource.ID_KEY), |
| toStringAttributeValueProto("my-instance")); |
| |
| private StackdriverV2ExporterHandler handler; |
| |
| @Before |
| public void setUp() throws IOException { |
| handler = |
| StackdriverV2ExporterHandler.createWithCredentials( |
| PROJECT_ID, |
| FAKE_CREDENTIALS, |
| Collections.<String, io.opencensus.trace.AttributeValue>emptyMap(), |
| DEFAULT_DEADLINE); |
| } |
| |
| @Test |
| public void generateSpan() { |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| SPAN_NAME, |
| null, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| TimeEvent annotationTimeEvent1 = |
| TimeEvent.newBuilder() |
| .setAnnotation( |
| TimeEvent.Annotation.newBuilder() |
| .setDescription( |
| TruncatableString.newBuilder().setValue(ANNOTATION_TEXT).build()) |
| .setAttributes(Span.Attributes.newBuilder().build()) |
| .build()) |
| .setTime( |
| com.google.protobuf.Timestamp.newBuilder() |
| .setSeconds(eventTimestamp1.getSeconds()) |
| .setNanos(eventTimestamp1.getNanos()) |
| .build()) |
| .build(); |
| TimeEvent annotationTimeEvent2 = |
| TimeEvent.newBuilder() |
| .setAnnotation( |
| TimeEvent.Annotation.newBuilder() |
| .setDescription( |
| TruncatableString.newBuilder().setValue(ANNOTATION_TEXT).build()) |
| .setAttributes(Span.Attributes.newBuilder().build()) |
| .build()) |
| .setTime( |
| com.google.protobuf.Timestamp.newBuilder() |
| .setSeconds(eventTimestamp3.getSeconds()) |
| .setNanos(eventTimestamp3.getNanos()) |
| .build()) |
| .build(); |
| |
| TimeEvent sentTimeEvent = |
| TimeEvent.newBuilder() |
| .setMessageEvent( |
| TimeEvent.MessageEvent.newBuilder() |
| .setType(MessageEvent.Type.SENT) |
| .setId(sentMessageEvent.getMessageId())) |
| .setTime( |
| com.google.protobuf.Timestamp.newBuilder() |
| .setSeconds(eventTimestamp2.getSeconds()) |
| .setNanos(eventTimestamp2.getNanos()) |
| .build()) |
| .build(); |
| TimeEvent recvTimeEvent = |
| TimeEvent.newBuilder() |
| .setMessageEvent( |
| TimeEvent.MessageEvent.newBuilder() |
| .setType(MessageEvent.Type.RECEIVED) |
| .setId(recvMessageEvent.getMessageId())) |
| .setTime( |
| com.google.protobuf.Timestamp.newBuilder() |
| .setSeconds(eventTimestamp1.getSeconds()) |
| .setNanos(eventTimestamp1.getNanos()) |
| .build()) |
| .build(); |
| |
| Span.Links spanLinks = |
| Span.Links.newBuilder() |
| .setDroppedLinksCount(DROPPED_LINKS_COUNT) |
| .addLink( |
| Span.Link.newBuilder() |
| .setType(Span.Link.Type.CHILD_LINKED_SPAN) |
| .setTraceId(TRACE_ID) |
| .setSpanId(SPAN_ID) |
| .setAttributes(Span.Attributes.newBuilder().build()) |
| .build()) |
| .build(); |
| |
| com.google.rpc.Status spanStatus = |
| com.google.rpc.Status.newBuilder() |
| .setCode(com.google.rpc.Code.DEADLINE_EXCEEDED.getNumber()) |
| .setMessage("TooSlow") |
| .build(); |
| |
| com.google.protobuf.Timestamp startTime = |
| com.google.protobuf.Timestamp.newBuilder() |
| .setSeconds(startTimestamp.getSeconds()) |
| .setNanos(startTimestamp.getNanos()) |
| .build(); |
| com.google.protobuf.Timestamp endTime = |
| com.google.protobuf.Timestamp.newBuilder() |
| .setSeconds(endTimestamp.getSeconds()) |
| .setNanos(endTimestamp.getNanos()) |
| .build(); |
| |
| Span span = |
| handler.generateSpan( |
| spanData, EMPTY_RESOURCE_LABELS, Collections.<String, AttributeValue>emptyMap()); |
| assertThat(span.getName()).isEqualTo(SD_SPAN_NAME); |
| assertThat(span.getSpanId()).isEqualTo(SPAN_ID); |
| assertThat(span.getParentSpanId()).isEqualTo(PARENT_SPAN_ID); |
| assertThat(span.getDisplayName()) |
| .isEqualTo(TruncatableString.newBuilder().setValue(SPAN_NAME).build()); |
| assertThat(span.getStartTime()).isEqualTo(startTime); |
| assertThat(span.getEndTime()).isEqualTo(endTime); |
| assertThat(span.getAttributes().getDroppedAttributesCount()) |
| .isEqualTo(DROPPED_ATTRIBUTES_COUNT); |
| // The generated attributes map contains more values (e.g. agent). We only test what we added. |
| assertThat(span.getAttributes().getAttributeMapMap()) |
| .containsEntry(ATTRIBUTE_KEY_1, AttributeValue.newBuilder().setIntValue(10L).build()); |
| assertThat(span.getAttributes().getAttributeMapMap()) |
| .containsEntry(ATTRIBUTE_KEY_2, AttributeValue.newBuilder().setBoolValue(true).build()); |
| // TODO(@Hailong): add stack trace test in the future. |
| assertThat(span.getStackTrace()).isEqualTo(StackTrace.newBuilder().build()); |
| assertThat(span.getTimeEvents().getDroppedMessageEventsCount()) |
| .isEqualTo(DROPPED_NETWORKEVENTS_COUNT); |
| assertThat(span.getTimeEvents().getDroppedAnnotationsCount()) |
| .isEqualTo(DROPPED_ANNOTATIONS_COUNT); |
| assertThat(span.getTimeEvents().getTimeEventList()) |
| .containsExactly(annotationTimeEvent1, annotationTimeEvent2, sentTimeEvent, recvTimeEvent); |
| assertThat(span.getLinks()).isEqualTo(spanLinks); |
| assertThat(span.getStatus()).isEqualTo(spanStatus); |
| assertThat(span.getSameProcessAsParentSpan()) |
| .isEqualTo(com.google.protobuf.BoolValue.of(false)); |
| assertThat(span.getChildSpanCount()) |
| .isEqualTo(Int32Value.newBuilder().setValue(CHILD_SPAN_COUNT).build()); |
| } |
| |
| @Test |
| public void getResourceLabels() { |
| Map<String, AttributeValue> actualLabels = |
| StackdriverV2ExporterHandler.getResourceLabels(CUSTOM_RESOURCE); |
| assertThat(actualLabels).containsExactlyEntriesIn(EXPECTED_RESOURCE_ATTRIBUTES); |
| } |
| |
| @Test |
| public void generateSpan_WithResourceLabels() { |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| SPAN_NAME, |
| null, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| Span span = |
| handler.generateSpan( |
| spanData, EXPECTED_RESOURCE_ATTRIBUTES, Collections.<String, AttributeValue>emptyMap()); |
| Map<String, AttributeValue> attributeMap = span.getAttributes().getAttributeMapMap(); |
| assertThat(attributeMap.entrySet()) |
| .containsAtLeastElementsIn(EXPECTED_RESOURCE_ATTRIBUTES.entrySet()); |
| } |
| |
| @Test |
| public void mapHttpAttributes() { |
| Map<String, io.opencensus.trace.AttributeValue> attributesMap = |
| new HashMap<String, io.opencensus.trace.AttributeValue>(); |
| |
| attributesMap.put("http.host", io.opencensus.trace.AttributeValue.stringAttributeValue("host")); |
| attributesMap.put( |
| "http.method", io.opencensus.trace.AttributeValue.stringAttributeValue("method")); |
| attributesMap.put("http.path", io.opencensus.trace.AttributeValue.stringAttributeValue("path")); |
| attributesMap.put( |
| "http.route", io.opencensus.trace.AttributeValue.stringAttributeValue("route")); |
| attributesMap.put( |
| "http.user_agent", io.opencensus.trace.AttributeValue.stringAttributeValue("user_agent")); |
| attributesMap.put( |
| "http.status_code", io.opencensus.trace.AttributeValue.longAttributeValue(200L)); |
| SpanData.Attributes httpAttributes = SpanData.Attributes.create(attributesMap, 0); |
| |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| SPAN_NAME, |
| null, |
| startTimestamp, |
| httpAttributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| |
| Span span = |
| handler.generateSpan( |
| spanData, EMPTY_RESOURCE_LABELS, Collections.<String, AttributeValue>emptyMap()); |
| Map<String, AttributeValue> attributes = span.getAttributes().getAttributeMapMap(); |
| |
| assertThat(attributes).containsEntry("/http/host", toStringAttributeValueProto("host")); |
| assertThat(attributes).containsEntry("/http/method", toStringAttributeValueProto("method")); |
| assertThat(attributes).containsEntry("/http/path", toStringAttributeValueProto("path")); |
| assertThat(attributes).containsEntry("/http/route", toStringAttributeValueProto("route")); |
| assertThat(attributes) |
| .containsEntry("/http/user_agent", toStringAttributeValueProto("user_agent")); |
| assertThat(attributes) |
| .containsEntry("/http/status_code", AttributeValue.newBuilder().setIntValue(200L).build()); |
| } |
| |
| @Test |
| public void exportChildSpanCount() { |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| SPAN_NAME, |
| Kind.SERVER, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| assertThat( |
| handler |
| .generateSpan( |
| spanData, EMPTY_RESOURCE_LABELS, Collections.<String, AttributeValue>emptyMap()) |
| .getChildSpanCount() |
| .getValue()) |
| .isEqualTo(CHILD_SPAN_COUNT); |
| } |
| |
| @Test |
| public void generateSpanName_ForServer() { |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| SPAN_NAME, |
| Kind.SERVER, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| assertThat( |
| handler |
| .generateSpan( |
| spanData, EMPTY_RESOURCE_LABELS, Collections.<String, AttributeValue>emptyMap()) |
| .getDisplayName() |
| .getValue()) |
| .isEqualTo("Recv." + SPAN_NAME); |
| } |
| |
| @Test |
| public void generateSpanName_ForServerWithRecv() { |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| "Recv." + SPAN_NAME, |
| Kind.SERVER, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| assertThat( |
| handler |
| .generateSpan( |
| spanData, EMPTY_RESOURCE_LABELS, Collections.<String, AttributeValue>emptyMap()) |
| .getDisplayName() |
| .getValue()) |
| .isEqualTo("Recv." + SPAN_NAME); |
| } |
| |
| @Test |
| public void generateSpanName_ForClient() { |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| SPAN_NAME, |
| Kind.CLIENT, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| assertThat( |
| handler |
| .generateSpan( |
| spanData, EMPTY_RESOURCE_LABELS, Collections.<String, AttributeValue>emptyMap()) |
| .getDisplayName() |
| .getValue()) |
| .isEqualTo("Sent." + SPAN_NAME); |
| } |
| |
| @Test |
| public void generateSpanName_ForClientWithSent() { |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| "Sent." + SPAN_NAME, |
| Kind.CLIENT, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| assertThat( |
| handler |
| .generateSpan( |
| spanData, EMPTY_RESOURCE_LABELS, Collections.<String, AttributeValue>emptyMap()) |
| .getDisplayName() |
| .getValue()) |
| .isEqualTo("Sent." + SPAN_NAME); |
| } |
| |
| @Test |
| public void addFixedAttributes() { |
| final ImmutableMap<String, AttributeValue> fixedAttributes = |
| ImmutableMap.of( |
| "string_attr_key", |
| toStringAttributeValueProto("my-project"), |
| "long_attr_key", |
| AttributeValue.newBuilder().setIntValue(1234).build(), |
| "bool_attr_key", |
| AttributeValue.newBuilder().setBoolValue(true).build()); |
| |
| SpanData spanData = |
| SpanData.create( |
| spanContext, |
| parentSpanId, |
| /* hasRemoteParent= */ true, |
| "Sent." + SPAN_NAME, |
| Kind.CLIENT, |
| startTimestamp, |
| attributes, |
| annotations, |
| messageEvents, |
| links, |
| CHILD_SPAN_COUNT, |
| status, |
| endTimestamp); |
| |
| Span span = handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS, fixedAttributes); |
| Map<String, AttributeValue> attributeMap = span.getAttributes().getAttributeMapMap(); |
| assertThat(attributeMap.entrySet()).containsAtLeastElementsIn(fixedAttributes.entrySet()); |
| } |
| } |