aboutsummaryrefslogtreecommitdiff
path: root/exporters/trace/ocagent
diff options
context:
space:
mode:
Diffstat (limited to 'exporters/trace/ocagent')
-rw-r--r--exporters/trace/ocagent/README.md48
-rw-r--r--exporters/trace/ocagent/build.gradle21
-rw-r--r--exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtils.java184
-rw-r--r--exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporter.java126
-rw-r--r--exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfiguration.java155
-rw-r--r--exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterHandler.java62
-rw-r--r--exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtils.java390
-rw-r--r--exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/package-info.java29
-rw-r--r--exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImpl.java169
-rw-r--r--exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImplTest.java109
-rw-r--r--exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtilsTest.java122
-rw-r--r--exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfigurationTest.java58
-rw-r--r--exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterTest.java54
-rw-r--r--exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtilsTest.java357
14 files changed, 1884 insertions, 0 deletions
diff --git a/exporters/trace/ocagent/README.md b/exporters/trace/ocagent/README.md
new file mode 100644
index 00000000..4f25bd6e
--- /dev/null
+++ b/exporters/trace/ocagent/README.md
@@ -0,0 +1,48 @@
+# OpenCensus Java OC-Agent Trace Exporter
+
+The *OpenCensus Java OC-Agent Trace Exporter* is the Java implementation of the OpenCensus Agent
+(OC-Agent) Trace Exporter.
+
+## Quickstart
+
+### Add the dependencies to your project
+
+For Maven add to your `pom.xml`:
+```xml
+<dependencies>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-api</artifactId>
+ <version>0.17.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-exporter-trace-ocagent</artifactId>
+ <version>0.17.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-impl</artifactId>
+ <version>0.17.0</version>
+ <scope>runtime</scope>
+ </dependency>
+</dependencies>
+```
+
+For Gradle add to your dependencies:
+```gradle
+compile 'io.opencensus:opencensus-api:0.17.0'
+compile 'io.opencensus:opencensus-exporter-trace-ocagent:0.17.0'
+runtime 'io.opencensus:opencensus-impl:0.17.0'
+```
+
+### Register the exporter
+
+```java
+public class MyMainClass {
+ public static void main(String[] args) throws Exception {
+ OcAgentTraceExporter.createAndRegister();
+ // ...
+ }
+}
+```
diff --git a/exporters/trace/ocagent/build.gradle b/exporters/trace/ocagent/build.gradle
new file mode 100644
index 00000000..777c08d0
--- /dev/null
+++ b/exporters/trace/ocagent/build.gradle
@@ -0,0 +1,21 @@
+description = 'OpenCensus Java OC-Agent Trace Exporter'
+
+[compileJava, compileTestJava].each() {
+ it.sourceCompatibility = 1.7
+ it.targetCompatibility = 1.7
+}
+
+dependencies {
+ compileOnly libraries.auto_value
+
+ compile project(':opencensus-api'),
+ project(':opencensus-contrib-monitored-resource-util'),
+ libraries.grpc_core,
+ libraries.grpc_netty,
+ libraries.grpc_stub,
+ libraries.opencensus_proto
+
+ testCompile project(':opencensus-api')
+
+ signature "org.codehaus.mojo.signature:java17:1.0@signature"
+}
diff --git a/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtils.java b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtils.java
new file mode 100644
index 00000000..65729803
--- /dev/null
+++ b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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.ocagent;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.common.OpenCensusLibraryInformation;
+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.proto.agent.common.v1.LibraryInfo;
+import io.opencensus.proto.agent.common.v1.LibraryInfo.Language;
+import io.opencensus.proto.agent.common.v1.Node;
+import io.opencensus.proto.agent.common.v1.ProcessIdentifier;
+import io.opencensus.proto.agent.common.v1.ServiceInfo;
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.SecureRandom;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/** Utilities for detecting and creating {@link Node}. */
+final class OcAgentNodeUtils {
+
+ // The current version of the OpenCensus OC-Agent Exporter.
+ @VisibleForTesting
+ static final String OC_AGENT_EXPORTER_VERSION = "0.17.0-SNAPSHOT"; // CURRENT_OPENCENSUS_VERSION
+
+ @VisibleForTesting static final String RESOURCE_TYPE_ATTRIBUTE_KEY = "OPENCENSUS_SOURCE_TYPE";
+ @VisibleForTesting static final String RESOURCE_LABEL_ATTRIBUTE_KEY = "OPENCENSUS_SOURCE_LABELS";
+
+ @Nullable
+ private static final MonitoredResource RESOURCE = MonitoredResourceUtils.getDefaultResource();
+
+ // Creates a Node with information from the OpenCensus library and environment variables.
+ static Node getNodeInfo(String serviceName) {
+ String jvmName = ManagementFactory.getRuntimeMXBean().getName();
+ Timestamp censusTimestamp = Timestamp.fromMillis(System.currentTimeMillis());
+ return Node.newBuilder()
+ .setIdentifier(getProcessIdentifier(jvmName, censusTimestamp))
+ .setLibraryInfo(getLibraryInfo(OpenCensusLibraryInformation.VERSION))
+ .setServiceInfo(getServiceInfo(serviceName))
+ .putAllAttributes(getAttributeMap(RESOURCE))
+ .build();
+ }
+
+ // Creates process identifier with the given JVM name and start time.
+ @VisibleForTesting
+ static ProcessIdentifier getProcessIdentifier(String jvmName, Timestamp censusTimestamp) {
+ String hostname;
+ int pid;
+ // jvmName should be something like '<pid>@<hostname>', at least in Oracle and OpenJdk JVMs
+ int delimiterIndex = jvmName.indexOf('@');
+ if (delimiterIndex < 1) {
+ // Not the expected format, generate a random number.
+ try {
+ hostname = InetAddress.getLocalHost().getHostName();
+ } catch (UnknownHostException e) {
+ hostname = "localhost";
+ }
+ // Generate a random number as the PID.
+ pid = new SecureRandom().nextInt();
+ } else {
+ hostname = jvmName.substring(delimiterIndex + 1, jvmName.length());
+ try {
+ pid = Integer.parseInt(jvmName.substring(0, delimiterIndex));
+ } catch (NumberFormatException e) {
+ // Generate a random number as the PID if format is unexpected.
+ pid = new SecureRandom().nextInt();
+ }
+ }
+
+ return ProcessIdentifier.newBuilder()
+ .setHostName(hostname)
+ .setPid(pid)
+ .setStartTimestamp(TraceProtoUtils.toTimestampProto(censusTimestamp))
+ .build();
+ }
+
+ // Creates library info with the given OpenCensus Java version.
+ @VisibleForTesting
+ static LibraryInfo getLibraryInfo(String currentOcJavaVersion) {
+ return LibraryInfo.newBuilder()
+ .setLanguage(Language.JAVA)
+ .setCoreLibraryVersion(currentOcJavaVersion)
+ .setExporterVersion(OC_AGENT_EXPORTER_VERSION)
+ .build();
+ }
+
+ // Creates service info with the given service name.
+ @VisibleForTesting
+ static ServiceInfo getServiceInfo(String serviceName) {
+ return ServiceInfo.newBuilder().setName(serviceName).build();
+ }
+
+ /*
+ * Creates an attribute map with the given MonitoredResource.
+ * If the given resource is not null, the attribute map contains exactly two entries:
+ *
+ * OPENCENSUS_SOURCE_TYPE:
+ * A string that describes the type of the resource prefixed by a domain namespace,
+ * e.g. “kubernetes.io/container”.
+ * OPENCENSUS_SOURCE_LABELS:
+ * A comma-separated list of labels describing the source in more detail,
+ * e.g. “key1=val1,key2=val2”. The allowed character set is appropriately constrained.
+ */
+ // TODO: update the resource attributes once we have an agreement on the resource specs:
+ // https://github.com/census-instrumentation/opencensus-specs/pull/162.
+ @VisibleForTesting
+ static Map<String, String> getAttributeMap(@Nullable MonitoredResource resource) {
+ if (resource == null) {
+ return Collections.emptyMap();
+ } else {
+ Map<String, String> resourceAttributes = new HashMap<String, String>();
+ resourceAttributes.put(RESOURCE_TYPE_ATTRIBUTE_KEY, resource.getResourceType().name());
+ resourceAttributes.put(RESOURCE_LABEL_ATTRIBUTE_KEY, getConcatenatedResourceLabels(resource));
+ return resourceAttributes;
+ }
+ }
+
+ // Encodes the attributes of MonitoredResource into a comma-separated list of labels.
+ // For example "aws_account=account1,instance_id=instance1,region=us-east-2".
+ private static String getConcatenatedResourceLabels(MonitoredResource resource) {
+ StringBuilder resourceLabels = new StringBuilder();
+ if (resource instanceof AwsEc2InstanceMonitoredResource) {
+ AwsEc2InstanceMonitoredResource awsEc2Resource = (AwsEc2InstanceMonitoredResource) resource;
+ putIntoBuilderIfHasValue(resourceLabels, "aws_account", awsEc2Resource.getAccount());
+ putIntoBuilderIfHasValue(resourceLabels, "instance_id", awsEc2Resource.getInstanceId());
+ putIntoBuilderIfHasValue(resourceLabels, "region", awsEc2Resource.getRegion());
+ } else if (resource instanceof GcpGceInstanceMonitoredResource) {
+ GcpGceInstanceMonitoredResource gceResource = (GcpGceInstanceMonitoredResource) resource;
+ putIntoBuilderIfHasValue(resourceLabels, "gcp_account", gceResource.getAccount());
+ putIntoBuilderIfHasValue(resourceLabels, "instance_id", gceResource.getInstanceId());
+ putIntoBuilderIfHasValue(resourceLabels, "zone", gceResource.getZone());
+ } else if (resource instanceof GcpGkeContainerMonitoredResource) {
+ GcpGkeContainerMonitoredResource gkeResource = (GcpGkeContainerMonitoredResource) resource;
+ putIntoBuilderIfHasValue(resourceLabels, "gcp_account", gkeResource.getAccount());
+ putIntoBuilderIfHasValue(resourceLabels, "instance_id", gkeResource.getInstanceId());
+ putIntoBuilderIfHasValue(resourceLabels, "location", gkeResource.getZone());
+ putIntoBuilderIfHasValue(resourceLabels, "namespace_name", gkeResource.getNamespaceId());
+ putIntoBuilderIfHasValue(resourceLabels, "cluster_name", gkeResource.getClusterName());
+ putIntoBuilderIfHasValue(resourceLabels, "container_name", gkeResource.getContainerName());
+ putIntoBuilderIfHasValue(resourceLabels, "pod_name", gkeResource.getPodId());
+ }
+ return resourceLabels.toString();
+ }
+
+ // If the given resourceValue is not empty, encodes resourceKey and resourceValue as
+ // "resourceKey:resourceValue" and puts it into the given StringBuilder. Otherwise skip the value.
+ private static void putIntoBuilderIfHasValue(
+ StringBuilder builder, String resourceKey, String resourceValue) {
+ if (resourceValue.isEmpty()) {
+ return;
+ }
+ if (!(builder.length() == 0)) {
+ // Appends the comma separator to the front, if the StringBuilder already has entries.
+ builder.append(',');
+ }
+ builder.append(resourceKey);
+ builder.append('=');
+ builder.append(resourceValue);
+ }
+
+ private OcAgentNodeUtils() {}
+}
diff --git a/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporter.java b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporter.java
new file mode 100644
index 00000000..5c468ded
--- /dev/null
+++ b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporter.java
@@ -0,0 +1,126 @@
+/*
+ * 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.ocagent;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.trace.Tracing;
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * The implementation of the OpenCensus Agent (OC-Agent) Trace Exporter.
+ *
+ * <p>Example of usage:
+ *
+ * <pre>{@code
+ * public static void main(String[] args) {
+ * OcAgentTraceExporter.createAndRegister();
+ * ... // Do work.
+ * }
+ * }</pre>
+ *
+ * @since 0.17
+ */
+@ThreadSafe
+public final class OcAgentTraceExporter {
+
+ private static final Object monitor = new Object();
+ private static final String REGISTER_NAME = OcAgentTraceExporter.class.getName();
+
+ @GuardedBy("monitor")
+ @Nullable
+ private static Handler handler = null;
+
+ private OcAgentTraceExporter() {}
+
+ /**
+ * Creates a {@code OcAgentTraceExporterHandler} with default configurations and registers it to
+ * the OpenCensus library.
+ *
+ * @since 0.17
+ */
+ public static void createAndRegister() {
+ synchronized (monitor) {
+ checkState(handler == null, "OC-Agent exporter is already registered.");
+ OcAgentTraceExporterHandler newHandler = new OcAgentTraceExporterHandler();
+ registerInternal(newHandler);
+ }
+ }
+
+ /**
+ * Creates a {@code OcAgentTraceExporterHandler} with the given configurations and registers it to
+ * the OpenCensus library.
+ *
+ * @param configuration the {@code OcAgentTraceExporterConfiguration}.
+ * @since 0.17
+ */
+ public static void createAndRegister(OcAgentTraceExporterConfiguration configuration) {
+ synchronized (monitor) {
+ checkState(handler == null, "OC-Agent exporter is already registered.");
+ OcAgentTraceExporterHandler newHandler =
+ new OcAgentTraceExporterHandler(
+ configuration.getEndPoint(),
+ configuration.getServiceName(),
+ configuration.getUseInsecure(),
+ configuration.getRetryInterval(),
+ configuration.getEnableConfig());
+ registerInternal(newHandler);
+ }
+ }
+
+ /**
+ * Registers the {@code OcAgentTraceExporterHandler}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} where this service is registered.
+ */
+ @VisibleForTesting
+ static void register(SpanExporter spanExporter, Handler handler) {
+ spanExporter.registerHandler(REGISTER_NAME, handler);
+ }
+
+ private static void registerInternal(Handler newHandler) {
+ synchronized (monitor) {
+ handler = newHandler;
+ register(Tracing.getExportComponent().getSpanExporter(), newHandler);
+ }
+ }
+
+ /**
+ * Unregisters the OC-Agent exporter from the OpenCensus library.
+ *
+ * @since 0.17
+ */
+ public static void unregister() {
+ unregister(Tracing.getExportComponent().getSpanExporter());
+ }
+
+ /**
+ * Unregisters the {@code OcAgentTraceExporterHandler}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} from where this service is
+ * unregistered.
+ */
+ @VisibleForTesting
+ static void unregister(SpanExporter spanExporter) {
+ spanExporter.unregisterHandler(REGISTER_NAME);
+ }
+}
diff --git a/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfiguration.java b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfiguration.java
new file mode 100644
index 00000000..c7bf1e95
--- /dev/null
+++ b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfiguration.java
@@ -0,0 +1,155 @@
+/*
+ * 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.ocagent;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Duration;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Configurations for {@link OcAgentTraceExporter}.
+ *
+ * @since 0.17
+ */
+@AutoValue
+@Immutable
+public abstract class OcAgentTraceExporterConfiguration {
+
+ OcAgentTraceExporterConfiguration() {}
+
+ /**
+ * Returns the end point of OC-Agent. The end point can be dns, ip:port, etc.
+ *
+ * @return the end point of OC-Agent.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract String getEndPoint();
+
+ /**
+ * Returns whether to disable client transport security for the exporter's gRPC connection or not.
+ *
+ * @return whether to disable client transport security for the exporter's gRPC connection or not.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Boolean getUseInsecure();
+
+ /**
+ * Returns the service name to be used for this {@link OcAgentTraceExporter}.
+ *
+ * @return the service name.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract String getServiceName();
+
+ /**
+ * Returns the retry time interval when trying to connect to Agent.
+ *
+ * @return the retry time interval.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Duration getRetryInterval();
+
+ /**
+ * Returns whether the {@link OcAgentTraceExporter} should handle the config streams.
+ *
+ * @return whether the {@code OcAgentTraceExporter} should handle the config streams.
+ * @since 0.17
+ */
+ public abstract boolean getEnableConfig();
+
+ /**
+ * Returns a new {@link Builder}.
+ *
+ * @return a {@code Builder}.
+ * @since 0.17
+ */
+ public static Builder builder() {
+ return new AutoValue_OcAgentTraceExporterConfiguration.Builder().setEnableConfig(true);
+ }
+
+ /**
+ * Builder for {@link OcAgentTraceExporterConfiguration}.
+ *
+ * @since 0.17
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ Builder() {}
+
+ /**
+ * Sets the end point of OC-Agent server.
+ *
+ * @param endPoint the end point of OC-Agent.
+ * @return this.
+ * @since 0.17
+ */
+ public abstract Builder setEndPoint(String endPoint);
+
+ /**
+ * Sets whether to disable client transport security for the exporter's gRPC connection or not.
+ *
+ * @param useInsecure whether disable client transport security for the exporter's gRPC
+ * connection.
+ * @return this.
+ * @since 0.17
+ */
+ public abstract Builder setUseInsecure(Boolean useInsecure);
+
+ /**
+ * Sets the service name to be used for this {@link OcAgentTraceExporter}.
+ *
+ * @param serviceName the service name.
+ * @return this.
+ * @since 0.17
+ */
+ public abstract Builder setServiceName(String serviceName);
+
+ /**
+ * Sets the retry time interval when trying to connect to Agent.
+ *
+ * @param retryInterval the retry time interval.
+ * @return this.
+ * @since 0.17
+ */
+ public abstract Builder setRetryInterval(Duration retryInterval);
+
+ /**
+ * Sets whether {@link OcAgentTraceExporter} should handle the config streams.
+ *
+ * @param enableConfig whether {@code OcAgentTraceExporter} should handle the config streams.
+ * @return this.
+ * @since 0.17
+ */
+ public abstract Builder setEnableConfig(boolean enableConfig);
+
+ // TODO(songya): add an option that controls whether to always keep the RPC connection alive.
+
+ /**
+ * Builds a {@link OcAgentTraceExporterConfiguration}.
+ *
+ * @return a {@code OcAgentTraceExporterConfiguration}.
+ * @since 0.17
+ */
+ public abstract OcAgentTraceExporterConfiguration build();
+ }
+}
diff --git a/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterHandler.java b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterHandler.java
new file mode 100644
index 00000000..5edc06df
--- /dev/null
+++ b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterHandler.java
@@ -0,0 +1,62 @@
+/*
+ * 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.ocagent;
+
+import io.opencensus.common.Duration;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import java.util.Collection;
+import javax.annotation.Nullable;
+
+/** Exporting handler for OC-Agent Tracing. */
+final class OcAgentTraceExporterHandler extends Handler {
+
+ private static final String DEFAULT_END_POINT = "localhost:55678";
+ private static final String DEFAULT_SERVICE_NAME = "OpenCensus";
+ private static final Duration DEFAULT_RETRY_INTERVAL = Duration.create(300, 0); // 5 minutes
+
+ OcAgentTraceExporterHandler() {
+ this(null, null, null, null, /* enableConfig= */ true);
+ }
+
+ OcAgentTraceExporterHandler(
+ @Nullable String endPoint,
+ @Nullable String serviceName,
+ @Nullable Boolean useInsecure,
+ @Nullable Duration retryInterval,
+ boolean enableConfig) {
+ // if (endPoint == null) {
+ // endPoint = DEFAULT_END_POINT;
+ // }
+ // if (serviceName == null) {
+ // serviceName = DEFAULT_SERVICE_NAME;
+ // }
+ // if (useInsecure == null) {
+ // useInsecure = false;
+ // }
+ // if (retryInterval == null) {
+ // retryInterval = DEFAULT_RETRY_INTERVAL;
+ // }
+ // OcAgentTraceServiceClients.startAttemptsToConnectToAgent(
+ // endPoint, useInsecure, serviceName, retryInterval.toMillis(), enableConfig);
+ }
+
+ @Override
+ public void export(Collection<SpanData> spanDataList) {
+ // OcAgentTraceServiceClients.onExport(spanDataList);
+ }
+}
diff --git a/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtils.java b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtils.java
new file mode 100644
index 00000000..ec778ba6
--- /dev/null
+++ b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtils.java
@@ -0,0 +1,390 @@
+/*
+ * 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.ocagent;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.protobuf.BoolValue;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.UInt32Value;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.proto.agent.trace.v1.UpdatedLibraryConfig;
+import io.opencensus.proto.trace.v1.AttributeValue;
+import io.opencensus.proto.trace.v1.ConstantSampler;
+import io.opencensus.proto.trace.v1.ProbabilitySampler;
+import io.opencensus.proto.trace.v1.Span;
+import io.opencensus.proto.trace.v1.Span.Attributes;
+import io.opencensus.proto.trace.v1.Span.Link;
+import io.opencensus.proto.trace.v1.Span.Links;
+import io.opencensus.proto.trace.v1.Span.SpanKind;
+import io.opencensus.proto.trace.v1.Span.TimeEvent;
+import io.opencensus.proto.trace.v1.Span.TimeEvent.MessageEvent;
+import io.opencensus.proto.trace.v1.Span.Tracestate;
+import io.opencensus.proto.trace.v1.Span.Tracestate.Entry;
+import io.opencensus.proto.trace.v1.Status;
+import io.opencensus.proto.trace.v1.TraceConfig;
+import io.opencensus.proto.trace.v1.TruncatableString;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.MessageEvent.Type;
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span.Kind;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.config.TraceParams;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import io.opencensus.trace.export.SpanData.TimedEvents;
+import io.opencensus.trace.samplers.Samplers;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/** Utilities for converting the Tracing data models in OpenCensus Java to/from OpenCensus Proto. */
+final class TraceProtoUtils {
+
+ // Constant functions for AttributeValue.
+ private static final Function<String, /*@Nullable*/ AttributeValue> stringAttributeValueFunction =
+ new Function<String, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(String stringValue) {
+ return AttributeValue.newBuilder()
+ .setStringValue(toTruncatableStringProto(stringValue))
+ .build();
+ }
+ };
+
+ private static final Function<Boolean, /*@Nullable*/ AttributeValue>
+ booleanAttributeValueFunction =
+ new Function<Boolean, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(Boolean booleanValue) {
+ return AttributeValue.newBuilder().setBoolValue(booleanValue).build();
+ }
+ };
+
+ private static final Function<Long, /*@Nullable*/ AttributeValue> longAttributeValueFunction =
+ new Function<Long, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(Long longValue) {
+ return AttributeValue.newBuilder().setIntValue(longValue).build();
+ }
+ };
+
+ private static final Function<Double, /*@Nullable*/ AttributeValue> doubleAttributeValueFunction =
+ new Function<Double, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(Double doubleValue) {
+ return AttributeValue.newBuilder().setDoubleValue(doubleValue).build();
+ }
+ };
+
+ /**
+ * Converts {@link SpanData} to {@link Span} proto.
+ *
+ * @param spanData the {@code SpanData}.
+ * @return proto representation of {@code Span}.
+ */
+ static Span toSpanProto(SpanData spanData) {
+ SpanContext spanContext = spanData.getContext();
+ TraceId traceId = spanContext.getTraceId();
+ SpanId spanId = spanContext.getSpanId();
+ Span.Builder spanBuilder =
+ Span.newBuilder()
+ .setTraceId(toByteString(traceId.getBytes()))
+ .setSpanId(toByteString(spanId.getBytes()))
+ .setTracestate(toTracestateProto(spanContext.getTracestate()))
+ .setName(toTruncatableStringProto(spanData.getName()))
+ .setStartTime(toTimestampProto(spanData.getStartTimestamp()))
+ .setAttributes(toAttributesProto(spanData.getAttributes()))
+ .setTimeEvents(
+ toTimeEventsProto(spanData.getAnnotations(), spanData.getMessageEvents()))
+ .setLinks(toLinksProto(spanData.getLinks()));
+
+ Kind kind = spanData.getKind();
+ if (kind != null) {
+ spanBuilder.setKind(toSpanKindProto(kind));
+ }
+
+ io.opencensus.trace.Status status = spanData.getStatus();
+ if (status != null) {
+ spanBuilder.setStatus(toStatusProto(status));
+ }
+
+ Timestamp end = spanData.getEndTimestamp();
+ if (end != null) {
+ spanBuilder.setEndTime(toTimestampProto(end));
+ }
+
+ Integer childSpanCount = spanData.getChildSpanCount();
+ if (childSpanCount != null) {
+ spanBuilder.setChildSpanCount(UInt32Value.newBuilder().setValue(childSpanCount).build());
+ }
+
+ Boolean hasRemoteParent = spanData.getHasRemoteParent();
+ if (hasRemoteParent != null) {
+ spanBuilder.setSameProcessAsParentSpan(BoolValue.of(!hasRemoteParent));
+ }
+
+ SpanId parentSpanId = spanData.getParentSpanId();
+ if (parentSpanId != null && parentSpanId.isValid()) {
+ spanBuilder.setParentSpanId(toByteString(parentSpanId.getBytes()));
+ }
+
+ return spanBuilder.build();
+ }
+
+ @VisibleForTesting
+ static ByteString toByteString(byte[] bytes) {
+ return ByteString.copyFrom(bytes);
+ }
+
+ private static Tracestate toTracestateProto(io.opencensus.trace.Tracestate tracestate) {
+ return Tracestate.newBuilder().addAllEntries(toEntriesProto(tracestate.getEntries())).build();
+ }
+
+ private static List<Entry> toEntriesProto(List<io.opencensus.trace.Tracestate.Entry> entries) {
+ List<Entry> entriesProto = new ArrayList<Entry>();
+ for (io.opencensus.trace.Tracestate.Entry entry : entries) {
+ entriesProto.add(
+ Entry.newBuilder().setKey(entry.getKey()).setValue(entry.getValue()).build());
+ }
+ return entriesProto;
+ }
+
+ private static SpanKind toSpanKindProto(Kind kind) {
+ switch (kind) {
+ case CLIENT:
+ return SpanKind.CLIENT;
+ case SERVER:
+ return SpanKind.SERVER;
+ }
+ return SpanKind.UNRECOGNIZED;
+ }
+
+ private static Span.TimeEvents toTimeEventsProto(
+ TimedEvents<Annotation> annotationTimedEvents,
+ TimedEvents<io.opencensus.trace.MessageEvent> messageEventTimedEvents) {
+ Span.TimeEvents.Builder timeEventsBuilder = Span.TimeEvents.newBuilder();
+ timeEventsBuilder.setDroppedAnnotationsCount(annotationTimedEvents.getDroppedEventsCount());
+ for (TimedEvent<Annotation> annotation : annotationTimedEvents.getEvents()) {
+ timeEventsBuilder.addTimeEvent(toTimeAnnotationProto(annotation));
+ }
+ timeEventsBuilder.setDroppedMessageEventsCount(messageEventTimedEvents.getDroppedEventsCount());
+ for (TimedEvent<io.opencensus.trace.MessageEvent> networkEvent :
+ messageEventTimedEvents.getEvents()) {
+ timeEventsBuilder.addTimeEvent(toTimeMessageEventProto(networkEvent));
+ }
+ return timeEventsBuilder.build();
+ }
+
+ private static TimeEvent toTimeAnnotationProto(TimedEvent<Annotation> timedEvent) {
+ TimeEvent.Builder timeEventBuilder =
+ TimeEvent.newBuilder().setTime(toTimestampProto(timedEvent.getTimestamp()));
+ Annotation annotation = timedEvent.getEvent();
+ timeEventBuilder.setAnnotation(
+ TimeEvent.Annotation.newBuilder()
+ .setDescription(toTruncatableStringProto(annotation.getDescription()))
+ .setAttributes(toAttributesBuilderProto(annotation.getAttributes(), 0))
+ .build());
+ return timeEventBuilder.build();
+ }
+
+ private static TimeEvent toTimeMessageEventProto(
+ TimedEvent<io.opencensus.trace.MessageEvent> timedEvent) {
+ TimeEvent.Builder timeEventBuilder =
+ TimeEvent.newBuilder().setTime(toTimestampProto(timedEvent.getTimestamp()));
+ io.opencensus.trace.MessageEvent messageEvent = timedEvent.getEvent();
+ timeEventBuilder.setMessageEvent(
+ TimeEvent.MessageEvent.newBuilder()
+ .setId(messageEvent.getMessageId())
+ .setCompressedSize(messageEvent.getCompressedMessageSize())
+ .setUncompressedSize(messageEvent.getUncompressedMessageSize())
+ .setType(toMessageEventTypeProto(messageEvent))
+ .build());
+ return timeEventBuilder.build();
+ }
+
+ private static TimeEvent.MessageEvent.Type toMessageEventTypeProto(
+ io.opencensus.trace.MessageEvent messageEvent) {
+ if (messageEvent.getType() == Type.RECEIVED) {
+ return MessageEvent.Type.RECEIVED;
+ } else {
+ return MessageEvent.Type.SENT;
+ }
+ }
+
+ private static Attributes toAttributesProto(
+ io.opencensus.trace.export.SpanData.Attributes attributes) {
+ Attributes.Builder attributesBuilder =
+ toAttributesBuilderProto(
+ attributes.getAttributeMap(), attributes.getDroppedAttributesCount());
+ return attributesBuilder.build();
+ }
+
+ private static Attributes.Builder toAttributesBuilderProto(
+ Map<String, io.opencensus.trace.AttributeValue> attributes, int droppedAttributesCount) {
+ Attributes.Builder attributesBuilder =
+ Attributes.newBuilder().setDroppedAttributesCount(droppedAttributesCount);
+ for (Map.Entry<String, io.opencensus.trace.AttributeValue> label : attributes.entrySet()) {
+ AttributeValue value = toAttributeValueProto(label.getValue());
+ if (value != null) {
+ attributesBuilder.putAttributeMap(label.getKey(), value);
+ }
+ }
+ return attributesBuilder;
+ }
+
+ @javax.annotation.Nullable
+ private static AttributeValue toAttributeValueProto(
+ io.opencensus.trace.AttributeValue attributeValue) {
+ return attributeValue.match(
+ stringAttributeValueFunction,
+ booleanAttributeValueFunction,
+ longAttributeValueFunction,
+ doubleAttributeValueFunction,
+ Functions.</*@Nullable*/ AttributeValue>returnNull());
+ }
+
+ private static Status toStatusProto(io.opencensus.trace.Status status) {
+ Status.Builder statusBuilder = Status.newBuilder().setCode(status.getCanonicalCode().value());
+ if (status.getDescription() != null) {
+ statusBuilder.setMessage(status.getDescription());
+ }
+ return statusBuilder.build();
+ }
+
+ @VisibleForTesting
+ static TruncatableString toTruncatableStringProto(String string) {
+ return TruncatableString.newBuilder().setValue(string).setTruncatedByteCount(0).build();
+ }
+
+ static com.google.protobuf.Timestamp toTimestampProto(Timestamp timestamp) {
+ return com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(timestamp.getSeconds())
+ .setNanos(timestamp.getNanos())
+ .build();
+ }
+
+ private static Link.Type toLinkTypeProto(io.opencensus.trace.Link.Type type) {
+ if (type == io.opencensus.trace.Link.Type.PARENT_LINKED_SPAN) {
+ return Link.Type.PARENT_LINKED_SPAN;
+ } else {
+ return Link.Type.CHILD_LINKED_SPAN;
+ }
+ }
+
+ private static Link toLinkProto(io.opencensus.trace.Link link) {
+ return Link.newBuilder()
+ .setTraceId(toByteString(link.getTraceId().getBytes()))
+ .setSpanId(toByteString(link.getSpanId().getBytes()))
+ .setType(toLinkTypeProto(link.getType()))
+ .setAttributes(toAttributesBuilderProto(link.getAttributes(), 0))
+ .build();
+ }
+
+ private static Links toLinksProto(io.opencensus.trace.export.SpanData.Links links) {
+ final Links.Builder linksBuilder =
+ Links.newBuilder().setDroppedLinksCount(links.getDroppedLinksCount());
+ for (io.opencensus.trace.Link link : links.getLinks()) {
+ linksBuilder.addLink(toLinkProto(link));
+ }
+ return linksBuilder.build();
+ }
+
+ /**
+ * Converts {@link TraceParams} to {@link TraceConfig}.
+ *
+ * @param traceParams the {@code TraceParams}.
+ * @return {@code TraceConfig}.
+ */
+ static TraceConfig toTraceConfigProto(TraceParams traceParams) {
+ TraceConfig.Builder traceConfigProtoBuilder = TraceConfig.newBuilder();
+ Sampler librarySampler = traceParams.getSampler();
+
+ if (Samplers.alwaysSample().equals(librarySampler)) {
+ traceConfigProtoBuilder.setConstantSampler(
+ ConstantSampler.newBuilder().setDecision(true).build());
+ } else if (Samplers.neverSample().equals(librarySampler)) {
+ traceConfigProtoBuilder.setConstantSampler(
+ ConstantSampler.newBuilder().setDecision(false).build());
+ } else {
+ // TODO: consider exposing the sampling probability of ProbabilitySampler.
+ double samplingProbability = parseSamplingProbability(librarySampler);
+ traceConfigProtoBuilder.setProbabilitySampler(
+ ProbabilitySampler.newBuilder().setSamplingProbability(samplingProbability).build());
+ } // TODO: add support for RateLimitingSampler.
+
+ return traceConfigProtoBuilder.build();
+ }
+
+ private static double parseSamplingProbability(Sampler sampler) {
+ String description = sampler.getDescription();
+ // description follows format "ProbabilitySampler{%.6f}", samplingProbability.
+ int leftParenIndex = description.indexOf("{");
+ int rightParenIndex = description.indexOf("}");
+ return Double.parseDouble(description.substring(leftParenIndex + 1, rightParenIndex));
+ }
+
+ /**
+ * Converts {@link TraceConfig} to {@link TraceParams}.
+ *
+ * @param traceConfigProto {@code TraceConfig}.
+ * @param currentTraceParams current {@code TraceParams}.
+ * @return updated {@code TraceParams}.
+ * @since 0.17
+ */
+ static TraceParams fromTraceConfigProto(
+ TraceConfig traceConfigProto, TraceParams currentTraceParams) {
+ TraceParams.Builder builder = currentTraceParams.toBuilder();
+ if (traceConfigProto.hasConstantSampler()) {
+ ConstantSampler constantSampler = traceConfigProto.getConstantSampler();
+ if (Boolean.TRUE.equals(constantSampler.getDecision())) {
+ builder.setSampler(Samplers.alwaysSample());
+ } else {
+ builder.setSampler(Samplers.neverSample());
+ }
+ } else if (traceConfigProto.hasProbabilitySampler()) {
+ builder.setSampler(
+ Samplers.probabilitySampler(
+ traceConfigProto.getProbabilitySampler().getSamplingProbability()));
+ } // TODO: add support for RateLimitingSampler.
+ return builder.build();
+ }
+
+ // Creates a TraceConfig proto message with current TraceParams.
+ static TraceConfig getCurrentTraceConfig(io.opencensus.trace.config.TraceConfig traceConfig) {
+ TraceParams traceParams = traceConfig.getActiveTraceParams();
+ return toTraceConfigProto(traceParams);
+ }
+
+ // Creates an updated TraceParams with the given UpdatedLibraryConfig message and current
+ // TraceParams, then applies the updated TraceParams.
+ static TraceParams getUpdatedTraceParams(
+ UpdatedLibraryConfig config, io.opencensus.trace.config.TraceConfig traceConfig) {
+ TraceParams currentParams = traceConfig.getActiveTraceParams();
+ TraceConfig traceConfigProto = config.getConfig();
+ return fromTraceConfigProto(traceConfigProto, currentParams);
+ }
+
+ private TraceProtoUtils() {}
+}
diff --git a/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/package-info.java b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/package-info.java
new file mode 100644
index 00000000..d01dd7eb
--- /dev/null
+++ b/exporters/trace/ocagent/src/main/java/io/opencensus/exporter/trace/ocagent/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/**
+ * This package contains the Java implementation of the OpenCensus Agent (OC-Agent) Trace Exporter.
+ *
+ * <p>WARNING: Currently all the public classes under this package are marked as {@link
+ * io.opencensus.common.ExperimentalApi}. The classes and APIs under {@link
+ * io.opencensus.exporter.trace.ocagent} are likely to get backwards-incompatible updates in the
+ * future. DO NOT USE except for experimental purposes.
+ *
+ * <p>See more details on
+ * https://github.com/census-instrumentation/opencensus-proto/tree/master/src/opencensus/proto/agent.
+ */
+@io.opencensus.common.ExperimentalApi
+package io.opencensus.exporter.trace.ocagent;
diff --git a/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImpl.java b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImpl.java
new file mode 100644
index 00000000..fbdb35e3
--- /dev/null
+++ b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImpl.java
@@ -0,0 +1,169 @@
+/*
+ * 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.ocagent;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.opencensus.proto.agent.trace.v1.CurrentLibraryConfig;
+import io.opencensus.proto.agent.trace.v1.ExportTraceServiceRequest;
+import io.opencensus.proto.agent.trace.v1.ExportTraceServiceResponse;
+import io.opencensus.proto.agent.trace.v1.TraceServiceGrpc;
+import io.opencensus.proto.agent.trace.v1.UpdatedLibraryConfig;
+import io.opencensus.proto.trace.v1.ConstantSampler;
+import io.opencensus.proto.trace.v1.TraceConfig;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/** Fake implementation of {@link TraceServiceGrpc}. */
+final class FakeOcAgentTraceServiceGrpcImpl extends TraceServiceGrpc.TraceServiceImplBase {
+
+ private static final Logger logger =
+ Logger.getLogger(FakeOcAgentTraceServiceGrpcImpl.class.getName());
+
+ // Default updatedLibraryConfig uses an always sampler.
+ private UpdatedLibraryConfig updatedLibraryConfig =
+ UpdatedLibraryConfig.newBuilder()
+ .setConfig(
+ TraceConfig.newBuilder()
+ .setConstantSampler(ConstantSampler.newBuilder().setDecision(true).build())
+ .build())
+ .build();
+
+ private final List<CurrentLibraryConfig> currentLibraryConfigs = new ArrayList<>();
+ private final List<ExportTraceServiceRequest> exportTraceServiceRequests = new ArrayList<>();
+
+ private final AtomicReference<StreamObserver<UpdatedLibraryConfig>> updatedConfigObserverRef =
+ new AtomicReference<>();
+
+ private final StreamObserver<CurrentLibraryConfig> currentConfigObserver =
+ new StreamObserver<CurrentLibraryConfig>() {
+ @Override
+ public void onNext(CurrentLibraryConfig value) {
+ currentLibraryConfigs.add(value);
+ @Nullable
+ StreamObserver<UpdatedLibraryConfig> updatedConfigObserver =
+ updatedConfigObserverRef.get();
+ if (updatedConfigObserver != null) {
+ updatedConfigObserver.onNext(updatedLibraryConfig);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ logger.warning("Exception thrown for config stream: " + t);
+ }
+
+ @Override
+ public void onCompleted() {}
+ };
+
+ private final StreamObserver<ExportTraceServiceRequest> exportRequestObserver =
+ new StreamObserver<ExportTraceServiceRequest>() {
+ @Override
+ public void onNext(ExportTraceServiceRequest value) {
+ exportTraceServiceRequests.add(value);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ logger.warning("Exception thrown for export stream: " + t);
+ }
+
+ @Override
+ public void onCompleted() {}
+ };
+
+ @Override
+ public StreamObserver<CurrentLibraryConfig> config(
+ StreamObserver<UpdatedLibraryConfig> updatedLibraryConfigStreamObserver) {
+ updatedConfigObserverRef.set(updatedLibraryConfigStreamObserver);
+ return currentConfigObserver;
+ }
+
+ @Override
+ public StreamObserver<ExportTraceServiceRequest> export(
+ StreamObserver<ExportTraceServiceResponse> exportTraceServiceResponseStreamObserver) {
+ return exportRequestObserver;
+ }
+
+ // Returns the stored CurrentLibraryConfigs.
+ List<CurrentLibraryConfig> getCurrentLibraryConfigs() {
+ return Collections.unmodifiableList(currentLibraryConfigs);
+ }
+
+ // Returns the stored ExportTraceServiceRequests.
+ List<ExportTraceServiceRequest> getExportTraceServiceRequests() {
+ return Collections.unmodifiableList(exportTraceServiceRequests);
+ }
+
+ // Sets the UpdatedLibraryConfig that will be passed to client.
+ void setUpdatedLibraryConfig(UpdatedLibraryConfig updatedLibraryConfig) {
+ this.updatedLibraryConfig = updatedLibraryConfig;
+ }
+
+ // Gets the UpdatedLibraryConfig that will be passed to client.
+ UpdatedLibraryConfig getUpdatedLibraryConfig() {
+ return updatedLibraryConfig;
+ }
+
+ static void startServer(String endPoint) throws IOException {
+ ServerBuilder<?> builder = NettyServerBuilder.forAddress(parseEndpoint(endPoint));
+ Executor executor = MoreExecutors.directExecutor();
+ builder.executor(executor);
+ final Server server = builder.addService(new FakeOcAgentTraceServiceGrpcImpl()).build();
+ server.start();
+ logger.info("Server started at " + endPoint);
+
+ Runtime.getRuntime()
+ .addShutdownHook(
+ new Thread() {
+ @Override
+ public void run() {
+ server.shutdown();
+ }
+ });
+
+ try {
+ server.awaitTermination();
+ } catch (InterruptedException e) {
+ logger.warning("Thread interrupted: " + e.getMessage());
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private static InetSocketAddress parseEndpoint(String endPoint) {
+ try {
+ int colonIndex = endPoint.indexOf(":");
+ String host = endPoint.substring(0, colonIndex);
+ int port = Integer.parseInt(endPoint.substring(colonIndex + 1));
+ return new InetSocketAddress(host, port);
+ } catch (RuntimeException e) {
+ logger.warning("Unexpected format of end point: " + endPoint + ", use default end point.");
+ return new InetSocketAddress("localhost", 55678);
+ }
+ }
+}
diff --git a/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImplTest.java b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImplTest.java
new file mode 100644
index 00000000..f619021b
--- /dev/null
+++ b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/FakeOcAgentTraceServiceGrpcImplTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ocagent;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.stub.StreamObserver;
+import io.opencensus.proto.agent.trace.v1.CurrentLibraryConfig;
+import io.opencensus.proto.agent.trace.v1.ExportTraceServiceRequest;
+import io.opencensus.proto.agent.trace.v1.ExportTraceServiceResponse;
+import io.opencensus.proto.agent.trace.v1.UpdatedLibraryConfig;
+import io.opencensus.proto.trace.v1.ConstantSampler;
+import io.opencensus.proto.trace.v1.TraceConfig;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link FakeOcAgentTraceServiceGrpcImpl}. */
+@RunWith(JUnit4.class)
+public class FakeOcAgentTraceServiceGrpcImplTest {
+
+ private final List<UpdatedLibraryConfig> updatedLibraryConfigs = new ArrayList<>();
+
+ private final StreamObserver<UpdatedLibraryConfig> updatedConfigObserver =
+ new StreamObserver<UpdatedLibraryConfig>() {
+
+ @Override
+ public void onNext(UpdatedLibraryConfig value) {
+ updatedLibraryConfigs.add(value);
+ }
+
+ @Override
+ public void onError(Throwable t) {}
+
+ @Override
+ public void onCompleted() {}
+ };
+
+ private final StreamObserver<ExportTraceServiceResponse> exportResponseObserver =
+ new StreamObserver<ExportTraceServiceResponse>() {
+ @Override
+ public void onNext(ExportTraceServiceResponse value) {}
+
+ @Override
+ public void onError(Throwable t) {}
+
+ @Override
+ public void onCompleted() {}
+ };
+
+ private static final UpdatedLibraryConfig neverSampledLibraryConfig =
+ UpdatedLibraryConfig.newBuilder()
+ .setConfig(
+ TraceConfig.newBuilder()
+ .setConstantSampler(ConstantSampler.newBuilder().setDecision(false).build())
+ .build())
+ .build();
+
+ @Test
+ public void export() {
+ FakeOcAgentTraceServiceGrpcImpl traceServiceGrpc = new FakeOcAgentTraceServiceGrpcImpl();
+ StreamObserver<ExportTraceServiceRequest> exportRequestObserver =
+ traceServiceGrpc.export(exportResponseObserver);
+ ExportTraceServiceRequest request = ExportTraceServiceRequest.getDefaultInstance();
+ exportRequestObserver.onNext(request);
+ assertThat(traceServiceGrpc.getExportTraceServiceRequests()).containsExactly(request);
+ }
+
+ @Test
+ public void config() {
+ FakeOcAgentTraceServiceGrpcImpl traceServiceGrpc = new FakeOcAgentTraceServiceGrpcImpl();
+ StreamObserver<CurrentLibraryConfig> currentConfigObsever =
+ traceServiceGrpc.config(updatedConfigObserver);
+ CurrentLibraryConfig currentLibraryConfig = CurrentLibraryConfig.getDefaultInstance();
+ currentConfigObsever.onNext(currentLibraryConfig);
+ assertThat(traceServiceGrpc.getCurrentLibraryConfigs()).containsExactly(currentLibraryConfig);
+ assertThat(updatedLibraryConfigs).containsExactly(traceServiceGrpc.getUpdatedLibraryConfig());
+ updatedLibraryConfigs.clear();
+ }
+
+ @Test
+ public void config_WithNeverSampler() {
+ FakeOcAgentTraceServiceGrpcImpl traceServiceGrpc = new FakeOcAgentTraceServiceGrpcImpl();
+ traceServiceGrpc.setUpdatedLibraryConfig(neverSampledLibraryConfig);
+ StreamObserver<CurrentLibraryConfig> currentConfigObsever =
+ traceServiceGrpc.config(updatedConfigObserver);
+ CurrentLibraryConfig currentLibraryConfig = CurrentLibraryConfig.getDefaultInstance();
+ currentConfigObsever.onNext(currentLibraryConfig);
+ assertThat(traceServiceGrpc.getCurrentLibraryConfigs()).containsExactly(currentLibraryConfig);
+ assertThat(updatedLibraryConfigs).containsExactly(neverSampledLibraryConfig);
+ updatedLibraryConfigs.clear();
+ }
+}
diff --git a/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtilsTest.java b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtilsTest.java
new file mode 100644
index 00000000..813066bc
--- /dev/null
+++ b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentNodeUtilsTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.ocagent;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.opencensus.exporter.trace.ocagent.OcAgentNodeUtils.OC_AGENT_EXPORTER_VERSION;
+import static io.opencensus.exporter.trace.ocagent.OcAgentNodeUtils.RESOURCE_LABEL_ATTRIBUTE_KEY;
+import static io.opencensus.exporter.trace.ocagent.OcAgentNodeUtils.RESOURCE_TYPE_ATTRIBUTE_KEY;
+
+import io.opencensus.common.Timestamp;
+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.proto.agent.common.v1.LibraryInfo;
+import io.opencensus.proto.agent.common.v1.LibraryInfo.Language;
+import io.opencensus.proto.agent.common.v1.ProcessIdentifier;
+import io.opencensus.proto.agent.common.v1.ServiceInfo;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link OcAgentNodeUtils}. */
+@RunWith(JUnit4.class)
+public class OcAgentNodeUtilsTest {
+
+ private static final AwsEc2InstanceMonitoredResource AWS_RESOURCE =
+ AwsEc2InstanceMonitoredResource.create("account1", "instance1", "us-east-2");
+ private static final GcpGceInstanceMonitoredResource GCE_RESOURCE =
+ GcpGceInstanceMonitoredResource.create("account2", "instance2", "us-west2");
+ private static final GcpGkeContainerMonitoredResource GKE_RESOURCE =
+ GcpGkeContainerMonitoredResource.create(
+ "account3", "cluster", "container", "", "instance3", "", "us-west4");
+
+ @Test
+ public void testConstants() {
+ assertThat(OC_AGENT_EXPORTER_VERSION).isEqualTo("0.17.0-SNAPSHOT");
+ assertThat(RESOURCE_TYPE_ATTRIBUTE_KEY).isEqualTo("OPENCENSUS_SOURCE_TYPE");
+ assertThat(RESOURCE_LABEL_ATTRIBUTE_KEY).isEqualTo("OPENCENSUS_SOURCE_LABELS");
+ }
+
+ @Test
+ public void getProcessIdentifier() {
+ String jvmName = "54321@my.org";
+ Timestamp timestamp = Timestamp.create(10, 20);
+ ProcessIdentifier processIdentifier = OcAgentNodeUtils.getProcessIdentifier(jvmName, timestamp);
+ assertThat(processIdentifier.getHostName()).isEqualTo("my.org");
+ assertThat(processIdentifier.getPid()).isEqualTo(54321);
+ assertThat(processIdentifier.getStartTimestamp())
+ .isEqualTo(com.google.protobuf.Timestamp.newBuilder().setSeconds(10).setNanos(20).build());
+ }
+
+ @Test
+ public void getLibraryInfo() {
+ String currentOcJavaVersion = "0.16.0";
+ LibraryInfo libraryInfo = OcAgentNodeUtils.getLibraryInfo(currentOcJavaVersion);
+ assertThat(libraryInfo.getLanguage()).isEqualTo(Language.JAVA);
+ assertThat(libraryInfo.getCoreLibraryVersion()).isEqualTo(currentOcJavaVersion);
+ assertThat(libraryInfo.getExporterVersion()).isEqualTo(OC_AGENT_EXPORTER_VERSION);
+ }
+
+ @Test
+ public void getServiceInfo() {
+ String serviceName = "my-service";
+ ServiceInfo serviceInfo = OcAgentNodeUtils.getServiceInfo(serviceName);
+ assertThat(serviceInfo.getName()).isEqualTo(serviceName);
+ }
+
+ @Test
+ public void getAttributeMap_Null() {
+ Map<String, String> attributeMap = OcAgentNodeUtils.getAttributeMap(null);
+ assertThat(attributeMap).isEmpty();
+ }
+
+ @Test
+ public void getAttributeMap_AwsEc2Resource() {
+ Map<String, String> attributeMap = OcAgentNodeUtils.getAttributeMap(AWS_RESOURCE);
+ assertThat(attributeMap)
+ .containsExactly(
+ RESOURCE_TYPE_ATTRIBUTE_KEY,
+ "AWS_EC2_INSTANCE",
+ RESOURCE_LABEL_ATTRIBUTE_KEY,
+ "aws_account=account1,instance_id=instance1,region=us-east-2");
+ }
+
+ @Test
+ public void getAttributeMap_GceResource() {
+ Map<String, String> attributeMap = OcAgentNodeUtils.getAttributeMap(GCE_RESOURCE);
+ assertThat(attributeMap)
+ .containsExactly(
+ RESOURCE_TYPE_ATTRIBUTE_KEY,
+ "GCP_GCE_INSTANCE",
+ RESOURCE_LABEL_ATTRIBUTE_KEY,
+ "gcp_account=account2,instance_id=instance2,zone=us-west2");
+ }
+
+ @Test
+ public void getAttributeMap_GkeResource() {
+ Map<String, String> attributeMap = OcAgentNodeUtils.getAttributeMap(GKE_RESOURCE);
+ assertThat(attributeMap)
+ .containsExactly(
+ RESOURCE_TYPE_ATTRIBUTE_KEY,
+ "GCP_GKE_CONTAINER",
+ RESOURCE_LABEL_ATTRIBUTE_KEY,
+ "gcp_account=account3,instance_id=instance3,location=us-west4,"
+ + "cluster_name=cluster,container_name=container");
+ }
+}
diff --git a/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfigurationTest.java b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfigurationTest.java
new file mode 100644
index 00000000..81bc5c60
--- /dev/null
+++ b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterConfigurationTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.ocagent;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Duration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link OcAgentTraceExporterConfiguration}. */
+@RunWith(JUnit4.class)
+public class OcAgentTraceExporterConfigurationTest {
+
+ @Test
+ public void defaultConfiguration() {
+ OcAgentTraceExporterConfiguration configuration =
+ OcAgentTraceExporterConfiguration.builder().build();
+ assertThat(configuration.getEndPoint()).isNull();
+ assertThat(configuration.getServiceName()).isNull();
+ assertThat(configuration.getUseInsecure()).isNull();
+ assertThat(configuration.getRetryInterval()).isNull();
+ assertThat(configuration.getEnableConfig()).isTrue();
+ }
+
+ @Test
+ public void setAndGet() {
+ Duration oneMinute = Duration.create(60, 0);
+ OcAgentTraceExporterConfiguration configuration =
+ OcAgentTraceExporterConfiguration.builder()
+ .setEndPoint("192.168.0.1:50051")
+ .setServiceName("service")
+ .setUseInsecure(true)
+ .setRetryInterval(oneMinute)
+ .setEnableConfig(false)
+ .build();
+ assertThat(configuration.getEndPoint()).isEqualTo("192.168.0.1:50051");
+ assertThat(configuration.getServiceName()).isEqualTo("service");
+ assertThat(configuration.getUseInsecure()).isTrue();
+ assertThat(configuration.getRetryInterval()).isEqualTo(oneMinute);
+ assertThat(configuration.getEnableConfig()).isFalse();
+ }
+}
diff --git a/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterTest.java b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterTest.java
new file mode 100644
index 00000000..c58acdb1
--- /dev/null
+++ b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/OcAgentTraceExporterTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ocagent;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link OcAgentTraceExporter}. */
+@RunWith(JUnit4.class)
+public class OcAgentTraceExporterTest {
+ @Mock private SpanExporter spanExporter;
+ @Mock private Handler handler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void registerUnregisterOcAgentTraceExporter() {
+ OcAgentTraceExporter.register(spanExporter, handler);
+ verify(spanExporter)
+ .registerHandler(
+ eq("io.opencensus.exporter.trace.ocagent.OcAgentTraceExporter"),
+ any(OcAgentTraceExporterHandler.class));
+ OcAgentTraceExporter.unregister(spanExporter);
+ verify(spanExporter)
+ .unregisterHandler(eq("io.opencensus.exporter.trace.ocagent.OcAgentTraceExporter"));
+ }
+}
diff --git a/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtilsTest.java b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtilsTest.java
new file mode 100644
index 00000000..74c7c29e
--- /dev/null
+++ b/exporters/trace/ocagent/src/test/java/io/opencensus/exporter/trace/ocagent/TraceProtoUtilsTest.java
@@ -0,0 +1,357 @@
+/*
+ * 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.ocagent;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.opencensus.exporter.trace.ocagent.TraceProtoUtils.toByteString;
+import static io.opencensus.exporter.trace.ocagent.TraceProtoUtils.toTruncatableStringProto;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.protobuf.BoolValue;
+import com.google.protobuf.UInt32Value;
+import io.opencensus.common.Timestamp;
+import io.opencensus.proto.agent.trace.v1.UpdatedLibraryConfig;
+import io.opencensus.proto.trace.v1.AttributeValue;
+import io.opencensus.proto.trace.v1.ConstantSampler;
+import io.opencensus.proto.trace.v1.ProbabilitySampler;
+import io.opencensus.proto.trace.v1.Span;
+import io.opencensus.proto.trace.v1.Span.SpanKind;
+import io.opencensus.proto.trace.v1.Span.TimeEvent;
+import io.opencensus.proto.trace.v1.Span.TimeEvent.MessageEvent;
+import io.opencensus.proto.trace.v1.TraceConfig;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Sampler;
+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.config.TraceParams;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import io.opencensus.trace.export.SpanData.TimedEvents;
+import io.opencensus.trace.samplers.Samplers;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link TraceProtoUtils}. */
+@RunWith(JUnit4.class)
+public class TraceProtoUtilsTest {
+
+ @Mock private io.opencensus.trace.config.TraceConfig mockTraceConfig;
+
+ private static final TraceParams DEFAULT_PARAMS = TraceParams.DEFAULT;
+
+ 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 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 ANNOTATION_TEXT = "MyAnnotationText";
+ private static final String ATTRIBUTE_KEY_1 = "MyAttributeKey1";
+ private static final String ATTRIBUTE_KEY_2 = "MyAttributeKey2";
+
+ private static final String FIRST_KEY = "key_1";
+ private static final String SECOND_KEY = "key_2";
+ private static final String FIRST_VALUE = "value.1";
+ private static final String SECOND_VALUE = "value.2";
+ private static final Tracestate multiValueTracestate =
+ Tracestate.builder().set(FIRST_KEY, FIRST_VALUE).set(SECOND_KEY, SECOND_VALUE).build();
+
+ 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, multiValueTracestate);
+
+ 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);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Mockito.when(mockTraceConfig.getActiveTraceParams()).thenReturn(DEFAULT_PARAMS);
+ Mockito.doNothing()
+ .when(mockTraceConfig)
+ .updateActiveTraceParams(Mockito.any(TraceParams.class));
+ }
+
+ @Test
+ public void toSpanProto() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ false,
+ SPAN_NAME,
+ Kind.CLIENT,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ TimeEvent annotationTimeEvent1 =
+ TimeEvent.newBuilder()
+ .setAnnotation(
+ TimeEvent.Annotation.newBuilder()
+ .setDescription(toTruncatableStringProto(ANNOTATION_TEXT))
+ .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(toTruncatableStringProto(ANNOTATION_TEXT))
+ .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(toByteString(traceId.getBytes()))
+ .setSpanId(toByteString(spanId.getBytes()))
+ .setAttributes(Span.Attributes.newBuilder().build())
+ .build())
+ .build();
+
+ io.opencensus.proto.trace.v1.Status spanStatus =
+ io.opencensus.proto.trace.v1.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 = TraceProtoUtils.toSpanProto(spanData);
+ assertThat(span.getName()).isEqualTo(toTruncatableStringProto(SPAN_NAME));
+ assertThat(span.getTraceId()).isEqualTo(toByteString(traceId.getBytes()));
+ assertThat(span.getSpanId()).isEqualTo(toByteString(spanId.getBytes()));
+ assertThat(span.getParentSpanId()).isEqualTo(toByteString(parentSpanId.getBytes()));
+ assertThat(span.getStartTime()).isEqualTo(startTime);
+ assertThat(span.getEndTime()).isEqualTo(endTime);
+ assertThat(span.getKind()).isEqualTo(SpanKind.CLIENT);
+ 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());
+ assertThat(span.getTimeEvents().getDroppedMessageEventsCount())
+ .isEqualTo(DROPPED_NETWORKEVENTS_COUNT);
+ assertThat(span.getTimeEvents().getDroppedAnnotationsCount())
+ .isEqualTo(DROPPED_ANNOTATIONS_COUNT);
+ assertThat(span.getTimeEvents().getTimeEventList())
+ .containsAllOf(annotationTimeEvent1, annotationTimeEvent2, sentTimeEvent, recvTimeEvent);
+ assertThat(span.getLinks()).isEqualTo(spanLinks);
+ assertThat(span.getStatus()).isEqualTo(spanStatus);
+ assertThat(span.getSameProcessAsParentSpan()).isEqualTo(BoolValue.of(true));
+ assertThat(span.getChildSpanCount())
+ .isEqualTo(UInt32Value.newBuilder().setValue(CHILD_SPAN_COUNT).build());
+ }
+
+ @Test
+ public void toTraceConfigProto_AlwaysSampler() {
+ assertThat(TraceProtoUtils.toTraceConfigProto(getTraceParams(Samplers.alwaysSample())))
+ .isEqualTo(
+ TraceConfig.newBuilder()
+ .setConstantSampler(ConstantSampler.newBuilder().setDecision(true).build())
+ .build());
+ }
+
+ @Test
+ public void toTraceConfigProto_NeverSampler() {
+ assertThat(TraceProtoUtils.toTraceConfigProto(getTraceParams(Samplers.neverSample())))
+ .isEqualTo(
+ TraceConfig.newBuilder()
+ .setConstantSampler(ConstantSampler.newBuilder().setDecision(false).build())
+ .build());
+ }
+
+ @Test
+ public void toTraceConfigProto_ProbabilitySampler() {
+ assertThat(TraceProtoUtils.toTraceConfigProto(getTraceParams(Samplers.probabilitySampler(0.5))))
+ .isEqualTo(
+ TraceConfig.newBuilder()
+ .setProbabilitySampler(
+ ProbabilitySampler.newBuilder().setSamplingProbability(0.5).build())
+ .build());
+ }
+
+ @Test
+ public void fromTraceConfigProto_AlwaysSampler() {
+ TraceConfig traceConfig =
+ TraceConfig.newBuilder()
+ .setConstantSampler(ConstantSampler.newBuilder().setDecision(true).build())
+ .build();
+ assertThat(TraceProtoUtils.fromTraceConfigProto(traceConfig, DEFAULT_PARAMS).getSampler())
+ .isEqualTo(Samplers.alwaysSample());
+ }
+
+ @Test
+ public void fromTraceConfigProto_NeverSampler() {
+ TraceConfig traceConfig =
+ TraceConfig.newBuilder()
+ .setConstantSampler(ConstantSampler.newBuilder().setDecision(false).build())
+ .build();
+ assertThat(TraceProtoUtils.fromTraceConfigProto(traceConfig, DEFAULT_PARAMS).getSampler())
+ .isEqualTo(Samplers.neverSample());
+ }
+
+ @Test
+ public void fromTraceConfigProto_ProbabilitySampler() {
+ TraceConfig traceConfig =
+ TraceConfig.newBuilder()
+ .setProbabilitySampler(
+ ProbabilitySampler.newBuilder().setSamplingProbability(0.01).build())
+ .build();
+ assertThat(TraceProtoUtils.fromTraceConfigProto(traceConfig, DEFAULT_PARAMS).getSampler())
+ .isEqualTo(Samplers.probabilitySampler(0.01));
+ }
+
+ @Test
+ public void getCurrentTraceConfig() {
+ TraceConfig configProto = TraceProtoUtils.toTraceConfigProto(DEFAULT_PARAMS);
+ assertThat(TraceProtoUtils.getCurrentTraceConfig(mockTraceConfig)).isEqualTo(configProto);
+ Mockito.verify(mockTraceConfig, Mockito.times(1)).getActiveTraceParams();
+ }
+
+ @Test
+ public void applyUpdatedConfig() {
+ TraceConfig configProto =
+ TraceConfig.newBuilder()
+ .setProbabilitySampler(
+ ProbabilitySampler.newBuilder().setSamplingProbability(0.01).build())
+ .build();
+ UpdatedLibraryConfig updatedLibraryConfig =
+ UpdatedLibraryConfig.newBuilder().setConfig(configProto).build();
+ TraceParams traceParams =
+ TraceProtoUtils.getUpdatedTraceParams(updatedLibraryConfig, mockTraceConfig);
+ TraceParams expectedParams =
+ DEFAULT_PARAMS.toBuilder().setSampler(Samplers.probabilitySampler(0.01)).build();
+ Mockito.verify(mockTraceConfig, Mockito.times(1)).getActiveTraceParams();
+ assertThat(traceParams).isEqualTo(expectedParams);
+ }
+
+ private static TraceParams getTraceParams(Sampler sampler) {
+ return DEFAULT_PARAMS.toBuilder().setSampler(sampler).build();
+ }
+}