diff --git a/buildscripts/import-control.xml b/buildscripts/import-control.xml
index 4fb0eac8a9..59c3e20558 100644
--- a/buildscripts/import-control.xml
+++ b/buildscripts/import-control.xml
@@ -141,6 +141,7 @@ General guidelines on imports:
+
diff --git a/exporters/trace/stackdriver/build.gradle b/exporters/trace/stackdriver/build.gradle
index fa4865616c..fc8bfef4b2 100644
--- a/exporters/trace/stackdriver/build.gradle
+++ b/exporters/trace/stackdriver/build.gradle
@@ -9,6 +9,7 @@ dependencies {
compileOnly libraries.auto_value
compile project(':opencensus-api'),
+ project(':opencensus-contrib-monitored-resource-util'),
libraries.google_auth
compile (libraries.google_cloud_trace) {
diff --git a/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java
index 3c3f783c07..0993fd8005 100644
--- a/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java
+++ b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java
@@ -42,6 +42,12 @@
import io.opencensus.common.OpenCensusLibraryInformation;
import io.opencensus.common.Scope;
import io.opencensus.common.Timestamp;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.AwsEc2InstanceMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGceInstanceMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGkeContainerMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResourceUtils;
+import io.opencensus.contrib.monitoredresource.util.ResourceType;
import io.opencensus.trace.Annotation;
import io.opencensus.trace.MessageEvent.Type;
import io.opencensus.trace.Sampler;
@@ -60,8 +66,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -91,6 +99,12 @@ final class StackdriverV2ExporterHandler extends SpanExporter.Handler {
.put("http.status_code", "/http/status_code")
.build();
+ @javax.annotation.Nullable
+ private static final MonitoredResource RESOURCE = MonitoredResourceUtils.getDefaultResource();
+
+ // Only initialize once.
+ private static final Map RESOURCE_LABELS = getResourceLabels(RESOURCE);
+
private final String projectId;
private final TraceServiceClient traceServiceClient;
private final ProjectName projectName;
@@ -122,7 +136,7 @@ static StackdriverV2ExporterHandler create(String projectId) throws IOException
}
@VisibleForTesting
- Span generateSpan(SpanData spanData) {
+ Span generateSpan(SpanData spanData, Map resourceLabels) {
SpanContext context = spanData.getContext();
final String traceIdHex = encodeTraceId(context.getTraceId());
final String spanIdHex = encodeSpanId(context.getSpanId());
@@ -135,7 +149,7 @@ Span generateSpan(SpanData spanData) {
.setDisplayName(
toTruncatableStringProto(toDisplayName(spanData.getName(), spanData.getKind())))
.setStartTime(toTimestampProto(spanData.getStartTimestamp()))
- .setAttributes(toAttributesProto(spanData.getAttributes()))
+ .setAttributes(toAttributesProto(spanData.getAttributes(), resourceLabels))
.setTimeEvents(
toTimeEventsProto(spanData.getAnnotations(), spanData.getMessageEvents()));
io.opencensus.trace.Status status = spanData.getStatus();
@@ -220,11 +234,15 @@ private static TimeEvent.MessageEvent.Type toMessageEventTypeProto(
// These are the attributes of the Span, where usually we may add more attributes like the agent.
private static Attributes toAttributesProto(
- io.opencensus.trace.export.SpanData.Attributes attributes) {
+ io.opencensus.trace.export.SpanData.Attributes attributes,
+ Map resourceLabels) {
Attributes.Builder attributesBuilder =
toAttributesBuilderProto(
attributes.getAttributeMap(), attributes.getDroppedAttributesCount());
attributesBuilder.putAttributeMap(AGENT_LABEL_KEY, AGENT_LABEL_VALUE);
+ for (Entry entry : resourceLabels.entrySet()) {
+ attributesBuilder.putAttributeMap(entry.getKey(), entry.getValue());
+ }
return attributesBuilder.build();
}
@@ -239,6 +257,119 @@ private static Attributes.Builder toAttributesBuilderProto(
return attributesBuilder;
}
+ // TODO(songya): make constructor of MonitoredResource public, and add unit tests for this method.
+ private static Map getResourceLabels(
+ @javax.annotation.Nullable MonitoredResource resource) {
+ if (resource == null) {
+ return Collections.emptyMap();
+ }
+ Map resourceLabels = new HashMap();
+ ResourceType resourceType = resource.getResourceType();
+ switch (resourceType) {
+ case AWS_EC2_INSTANCE:
+ AwsEc2InstanceMonitoredResource awsEc2InstanceMonitoredResource =
+ (AwsEc2InstanceMonitoredResource) resource;
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "aws_account",
+ awsEc2InstanceMonitoredResource.getAccount());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "instance_id",
+ awsEc2InstanceMonitoredResource.getInstanceId());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "region",
+ "aws:" + awsEc2InstanceMonitoredResource.getRegion());
+ return Collections.unmodifiableMap(resourceLabels);
+ case GCP_GCE_INSTANCE:
+ GcpGceInstanceMonitoredResource gcpGceInstanceMonitoredResource =
+ (GcpGceInstanceMonitoredResource) resource;
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "project_id",
+ gcpGceInstanceMonitoredResource.getAccount());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "instance_id",
+ gcpGceInstanceMonitoredResource.getInstanceId());
+ putToResourceAttributeMap(
+ resourceLabels, resourceType, "zone", gcpGceInstanceMonitoredResource.getZone());
+ return Collections.unmodifiableMap(resourceLabels);
+ case GCP_GKE_CONTAINER:
+ GcpGkeContainerMonitoredResource gcpGkeContainerMonitoredResource =
+ (GcpGkeContainerMonitoredResource) resource;
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "project_id",
+ gcpGkeContainerMonitoredResource.getAccount());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "instance_id",
+ gcpGkeContainerMonitoredResource.getInstanceId());
+ putToResourceAttributeMap(
+ resourceLabels, resourceType, "zone", gcpGkeContainerMonitoredResource.getZone());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "cluster_name",
+ gcpGkeContainerMonitoredResource.getClusterName());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "container_name",
+ gcpGkeContainerMonitoredResource.getContainerName());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "namespace_id",
+ gcpGkeContainerMonitoredResource.getNamespaceId());
+ putToResourceAttributeMap(
+ resourceLabels, resourceType, "pod_id", gcpGkeContainerMonitoredResource.getPodId());
+ return Collections.unmodifiableMap(resourceLabels);
+ }
+ return Collections.emptyMap();
+ }
+
+ private static void putToResourceAttributeMap(
+ Map map,
+ ResourceType resourceType,
+ String attributeName,
+ String attributeValue) {
+ map.put(
+ createResourceLabelKey(resourceType, attributeName),
+ toStringAttributeValueProto(attributeValue));
+ }
+
+ @VisibleForTesting
+ static String createResourceLabelKey(ResourceType resourceType, String resourceAttribute) {
+ return String.format("g.co/r/%s/%s", mapToStringResourceType(resourceType), resourceAttribute);
+ }
+
+ private static String mapToStringResourceType(ResourceType resourceType) {
+ switch (resourceType) {
+ case GCP_GCE_INSTANCE:
+ return "gce_instance";
+ case GCP_GKE_CONTAINER:
+ return "gke_container";
+ case AWS_EC2_INSTANCE:
+ return "aws_ec2_instance";
+ }
+ throw new IllegalArgumentException("Unknown resource type.");
+ }
+
+ @VisibleForTesting
+ static AttributeValue toStringAttributeValueProto(String value) {
+ return AttributeValue.newBuilder().setStringValue(toTruncatableStringProto(value)).build();
+ }
+
private static String mapKey(String key) {
if (HTTP_ATTRIBUTE_MAPPING.containsKey(key)) {
return HTTP_ATTRIBUTE_MAPPING.get(key);
@@ -347,7 +478,7 @@ public void export(Collection spanDataList) {
.startScopedSpan()) {
List spans = new ArrayList<>(spanDataList.size());
for (SpanData spanData : spanDataList) {
- spans.add(generateSpan(spanData));
+ spans.add(generateSpan(spanData, RESOURCE_LABELS));
}
// Sync call because it is already called for a batch of data, and on a separate thread.
// TODO(bdrutu): Consider to make this async in the future.
diff --git a/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java
index f20d1021af..6674dd9153 100644
--- a/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java
+++ b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java
@@ -17,6 +17,11 @@
package io.opencensus.exporter.trace.stackdriver;
import static com.google.common.truth.Truth.assertThat;
+import static io.opencensus.contrib.monitoredresource.util.ResourceType.AWS_EC2_INSTANCE;
+import static io.opencensus.contrib.monitoredresource.util.ResourceType.GCP_GCE_INSTANCE;
+import static io.opencensus.contrib.monitoredresource.util.ResourceType.GCP_GKE_CONTAINER;
+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;
@@ -43,6 +48,7 @@
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;
@@ -119,6 +125,47 @@ public final class StackdriverV2ExporterHandlerProtoTest {
private static final TimedEvents messageEvents =
TimedEvents.create(networkEventsList, DROPPED_NETWORKEVENTS_COUNT);
private static final SpanData.Links links = SpanData.Links.create(linksList, DROPPED_LINKS_COUNT);
+ private static final Map EMPTY_RESOURCE_LABELS = Collections.emptyMap();
+ private static final ImmutableMap AWS_RESOURCE_LABELS =
+ ImmutableMap.of(
+ createResourceLabelKey(AWS_EC2_INSTANCE, "aws_account"),
+ toStringAttributeValueProto("my-project"),
+ createResourceLabelKey(AWS_EC2_INSTANCE, "instance_id"),
+ toStringAttributeValueProto("my-instance"),
+ createResourceLabelKey(AWS_EC2_INSTANCE, "region"),
+ toStringAttributeValueProto("us-east-1"));
+ private static final ImmutableMap GCE_RESOURCE_LABELS =
+ ImmutableMap.of(
+ createResourceLabelKey(GCP_GCE_INSTANCE, "project_id"),
+ toStringAttributeValueProto("my-project"),
+ createResourceLabelKey(GCP_GCE_INSTANCE, "instance_id"),
+ toStringAttributeValueProto("my-instance"),
+ createResourceLabelKey(GCP_GCE_INSTANCE, "zone"),
+ toStringAttributeValueProto("us-east1"));
+ private static final ImmutableMap GKE_RESOURCE_LABELS =
+ ImmutableMap.builder()
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "project_id"),
+ toStringAttributeValueProto("my-project"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "cluster_name"),
+ toStringAttributeValueProto("cluster"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "container_name"),
+ toStringAttributeValueProto("container"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "namespace_id"),
+ toStringAttributeValueProto("namespace"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "instance_id"),
+ toStringAttributeValueProto("my-instance"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "pod_id"),
+ toStringAttributeValueProto("pod"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "zone"),
+ toStringAttributeValueProto("us-east1"))
+ .build();
private StackdriverV2ExporterHandler handler;
@@ -227,7 +274,7 @@ public void generateSpan() {
.setNanos(endTimestamp.getNanos())
.build();
- Span span = handler.generateSpan(spanData);
+ Span span = handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS);
assertThat(span.getName()).isEqualTo(SD_SPAN_NAME);
assertThat(span.getSpanId()).isEqualTo(SPAN_ID);
assertThat(span.getParentSpanId()).isEqualTo(PARENT_SPAN_ID);
@@ -258,6 +305,42 @@ public void generateSpan() {
.isEqualTo(Int32Value.newBuilder().setValue(CHILD_SPAN_COUNT).build());
}
+ @Test
+ public void generateSpan_WithAwsEc2ResourceLabels() {
+ generateSpan_WithResourceLabels(AWS_RESOURCE_LABELS);
+ }
+
+ @Test
+ public void generateSpan_WithGceResourceLabels() {
+ generateSpan_WithResourceLabels(GCE_RESOURCE_LABELS);
+ }
+
+ @Test
+ public void generateSpan_WithGkeResourceLabels() {
+ generateSpan_WithResourceLabels(GKE_RESOURCE_LABELS);
+ }
+
+ private void generateSpan_WithResourceLabels(Map resourceLabels) {
+ 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, resourceLabels);
+ Map attributeMap = span.getAttributes().getAttributeMapMap();
+ assertThat(attributeMap.entrySet()).containsAllIn(resourceLabels.entrySet());
+ }
+
@Test
public void mapHttpAttributes() {
Map attributesMap =
@@ -290,14 +373,15 @@ public void mapHttpAttributes() {
status,
endTimestamp);
- Span span = handler.generateSpan(spanData);
+ Span span = handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS);
Map attributes = span.getAttributes().getAttributeMapMap();
- assertThat(attributes).containsEntry("/http/host", toStringValue("host"));
- assertThat(attributes).containsEntry("/http/method", toStringValue("method"));
- assertThat(attributes).containsEntry("/http/path", toStringValue("path"));
- assertThat(attributes).containsEntry("/http/route", toStringValue("route"));
- assertThat(attributes).containsEntry("/http/user_agent", toStringValue("user_agent"));
+ 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());
}
@@ -319,7 +403,7 @@ public void generateSpanName_ForServer() {
CHILD_SPAN_COUNT,
status,
endTimestamp);
- assertThat(handler.generateSpan(spanData).getDisplayName().getValue())
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
.isEqualTo("Recv." + SPAN_NAME);
}
@@ -340,7 +424,7 @@ public void generateSpanName_ForServerWithRecv() {
CHILD_SPAN_COUNT,
status,
endTimestamp);
- assertThat(handler.generateSpan(spanData).getDisplayName().getValue())
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
.isEqualTo("Recv." + SPAN_NAME);
}
@@ -361,7 +445,7 @@ public void generateSpanName_ForClient() {
CHILD_SPAN_COUNT,
status,
endTimestamp);
- assertThat(handler.generateSpan(spanData).getDisplayName().getValue())
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
.isEqualTo("Sent." + SPAN_NAME);
}
@@ -382,14 +466,7 @@ public void generateSpanName_ForClientWithSent() {
CHILD_SPAN_COUNT,
status,
endTimestamp);
- assertThat(handler.generateSpan(spanData).getDisplayName().getValue())
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
.isEqualTo("Sent." + SPAN_NAME);
}
-
- private static AttributeValue toStringValue(String value) {
- return AttributeValue.newBuilder()
- .setStringValue(
- TruncatableString.newBuilder().setValue(value).setTruncatedByteCount(0).build())
- .build();
- }
}