aboutsummaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
authorJulien Desprez <jdesprez@google.com>2018-10-22 11:37:22 -0700
committerandroid-build-merger <android-build-merger@google.com>2018-10-22 11:37:22 -0700
commit13217871fefa43f6d16fbb31b04e9904996d87d5 (patch)
treeede84fcf0a9687d4907ae5f8a4788271d62e0922 /api
parentcfbefd32336596ea63784607e4106dc37ce0567f (diff)
parent6fbc3cf5a1a3369fd354c1e5d9f90c86e4bce0a4 (diff)
downloadopencensus-java-13217871fefa43f6d16fbb31b04e9904996d87d5.tar.gz
Merge remote-tracking branch 'aosp/upstream-master' into merge am: dd3cabeacc
am: 6fbc3cf5a1 Change-Id: I11b0ec1cf561d2a14da78e444b1594f167787fe6
Diffstat (limited to 'api')
-rw-r--r--api/README.md6
-rw-r--r--api/build.gradle15
-rw-r--r--api/src/main/java/io/opencensus/common/Clock.java43
-rw-r--r--api/src/main/java/io/opencensus/common/Duration.java136
-rw-r--r--api/src/main/java/io/opencensus/common/ExperimentalApi.java58
-rw-r--r--api/src/main/java/io/opencensus/common/Function.java38
-rw-r--r--api/src/main/java/io/opencensus/common/Functions.java130
-rw-r--r--api/src/main/java/io/opencensus/common/Internal.java42
-rw-r--r--api/src/main/java/io/opencensus/common/NonThrowingCloseable.java42
-rw-r--r--api/src/main/java/io/opencensus/common/OpenCensusLibraryInformation.java35
-rw-r--r--api/src/main/java/io/opencensus/common/Scope.java37
-rw-r--r--api/src/main/java/io/opencensus/common/ServerStats.java86
-rw-r--r--api/src/main/java/io/opencensus/common/ServerStatsDeserializationException.java47
-rw-r--r--api/src/main/java/io/opencensus/common/ServerStatsEncoding.java125
-rw-r--r--api/src/main/java/io/opencensus/common/ServerStatsFieldEnums.java159
-rw-r--r--api/src/main/java/io/opencensus/common/TimeUtils.java59
-rw-r--r--api/src/main/java/io/opencensus/common/Timestamp.java200
-rw-r--r--api/src/main/java/io/opencensus/common/ToDoubleFunction.java41
-rw-r--r--api/src/main/java/io/opencensus/common/ToLongFunction.java40
-rw-r--r--api/src/main/java/io/opencensus/common/package-info.java18
-rw-r--r--api/src/main/java/io/opencensus/internal/DefaultVisibilityForTesting.java37
-rw-r--r--api/src/main/java/io/opencensus/internal/NoopScope.java38
-rw-r--r--api/src/main/java/io/opencensus/internal/Provider.java49
-rw-r--r--api/src/main/java/io/opencensus/internal/StringUtils.java42
-rw-r--r--api/src/main/java/io/opencensus/internal/Utils.java191
-rw-r--r--api/src/main/java/io/opencensus/internal/ZeroTimeClock.java49
-rw-r--r--api/src/main/java/io/opencensus/internal/package-info.java24
-rw-r--r--api/src/main/java/io/opencensus/metrics/DerivedDoubleGauge.java152
-rw-r--r--api/src/main/java/io/opencensus/metrics/DerivedLongGauge.java150
-rw-r--r--api/src/main/java/io/opencensus/metrics/DoubleGauge.java213
-rw-r--r--api/src/main/java/io/opencensus/metrics/LabelKey.java62
-rw-r--r--api/src/main/java/io/opencensus/metrics/LabelValue.java57
-rw-r--r--api/src/main/java/io/opencensus/metrics/LongGauge.java205
-rw-r--r--api/src/main/java/io/opencensus/metrics/MetricRegistry.java156
-rw-r--r--api/src/main/java/io/opencensus/metrics/Metrics.java96
-rw-r--r--api/src/main/java/io/opencensus/metrics/MetricsComponent.java71
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/Distribution.java345
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/ExportComponent.java60
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/Metric.java137
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/MetricDescriptor.java173
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/MetricProducer.java40
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/MetricProducerManager.java88
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/Point.java63
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/Summary.java187
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/TimeSeries.java127
-rw-r--r--api/src/main/java/io/opencensus/metrics/export/Value.java246
-rw-r--r--api/src/main/java/io/opencensus/metrics/package-info.java32
-rw-r--r--api/src/main/java/io/opencensus/stats/Aggregation.java239
-rw-r--r--api/src/main/java/io/opencensus/stats/AggregationData.java555
-rw-r--r--api/src/main/java/io/opencensus/stats/BucketBoundaries.java66
-rw-r--r--api/src/main/java/io/opencensus/stats/Measure.java177
-rw-r--r--api/src/main/java/io/opencensus/stats/MeasureMap.java92
-rw-r--r--api/src/main/java/io/opencensus/stats/Measurement.java130
-rw-r--r--api/src/main/java/io/opencensus/stats/NoopStats.java221
-rw-r--r--api/src/main/java/io/opencensus/stats/Stats.java126
-rw-r--r--api/src/main/java/io/opencensus/stats/StatsCollectionState.java44
-rw-r--r--api/src/main/java/io/opencensus/stats/StatsComponent.java74
-rw-r--r--api/src/main/java/io/opencensus/stats/StatsRecorder.java34
-rw-r--r--api/src/main/java/io/opencensus/stats/View.java306
-rw-r--r--api/src/main/java/io/opencensus/stats/ViewData.java461
-rw-r--r--api/src/main/java/io/opencensus/stats/ViewManager.java61
-rw-r--r--api/src/main/java/io/opencensus/stats/package-info.java20
-rw-r--r--api/src/main/java/io/opencensus/tags/InternalUtils.java38
-rw-r--r--api/src/main/java/io/opencensus/tags/NoopTags.java214
-rw-r--r--api/src/main/java/io/opencensus/tags/Tag.java60
-rw-r--r--api/src/main/java/io/opencensus/tags/TagContext.java109
-rw-r--r--api/src/main/java/io/opencensus/tags/TagContextBuilder.java65
-rw-r--r--api/src/main/java/io/opencensus/tags/TagKey.java84
-rw-r--r--api/src/main/java/io/opencensus/tags/TagValue.java79
-rw-r--r--api/src/main/java/io/opencensus/tags/Tagger.java86
-rw-r--r--api/src/main/java/io/opencensus/tags/TaggingState.java48
-rw-r--r--api/src/main/java/io/opencensus/tags/Tags.java126
-rw-r--r--api/src/main/java/io/opencensus/tags/TagsComponent.java73
-rw-r--r--api/src/main/java/io/opencensus/tags/package-info.java32
-rw-r--r--api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java57
-rw-r--r--api/src/main/java/io/opencensus/tags/propagation/TagContextDeserializationException.java49
-rw-r--r--api/src/main/java/io/opencensus/tags/propagation/TagContextSerializationException.java49
-rw-r--r--api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java36
-rw-r--r--api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java56
-rw-r--r--api/src/main/java/io/opencensus/trace/Annotation.java83
-rw-r--r--api/src/main/java/io/opencensus/trace/AttributeValue.java255
-rw-r--r--api/src/main/java/io/opencensus/trace/BaseMessageEvent.java37
-rw-r--r--api/src/main/java/io/opencensus/trace/BlankSpan.java102
-rw-r--r--api/src/main/java/io/opencensus/trace/CurrentSpanUtils.java180
-rw-r--r--api/src/main/java/io/opencensus/trace/EndSpanOptions.java127
-rw-r--r--api/src/main/java/io/opencensus/trace/Link.java124
-rw-r--r--api/src/main/java/io/opencensus/trace/LowerCaseBase16Encoding.java91
-rw-r--r--api/src/main/java/io/opencensus/trace/MessageEvent.java151
-rw-r--r--api/src/main/java/io/opencensus/trace/NetworkEvent.java200
-rw-r--r--api/src/main/java/io/opencensus/trace/Sampler.java61
-rw-r--r--api/src/main/java/io/opencensus/trace/Span.java288
-rw-r--r--api/src/main/java/io/opencensus/trace/SpanBuilder.java356
-rw-r--r--api/src/main/java/io/opencensus/trace/SpanContext.java165
-rw-r--r--api/src/main/java/io/opencensus/trace/SpanId.java214
-rw-r--r--api/src/main/java/io/opencensus/trace/Status.java469
-rw-r--r--api/src/main/java/io/opencensus/trace/TraceComponent.java118
-rw-r--r--api/src/main/java/io/opencensus/trace/TraceId.java236
-rw-r--r--api/src/main/java/io/opencensus/trace/TraceOptions.java280
-rw-r--r--api/src/main/java/io/opencensus/trace/Tracer.java370
-rw-r--r--api/src/main/java/io/opencensus/trace/Tracestate.java273
-rw-r--r--api/src/main/java/io/opencensus/trace/Tracing.java125
-rw-r--r--api/src/main/java/io/opencensus/trace/config/TraceConfig.java64
-rw-r--r--api/src/main/java/io/opencensus/trace/config/TraceParams.java219
-rw-r--r--api/src/main/java/io/opencensus/trace/export/ExportComponent.java95
-rw-r--r--api/src/main/java/io/opencensus/trace/export/RunningSpanStore.java201
-rw-r--r--api/src/main/java/io/opencensus/trace/export/SampledSpanStore.java525
-rw-r--r--api/src/main/java/io/opencensus/trace/export/SpanData.java477
-rw-r--r--api/src/main/java/io/opencensus/trace/export/SpanExporter.java96
-rw-r--r--api/src/main/java/io/opencensus/trace/internal/BaseMessageEventUtils.java81
-rw-r--r--api/src/main/java/io/opencensus/trace/package-info.java33
-rw-r--r--api/src/main/java/io/opencensus/trace/propagation/BinaryFormat.java156
-rw-r--r--api/src/main/java/io/opencensus/trace/propagation/PropagationComponent.java73
-rw-r--r--api/src/main/java/io/opencensus/trace/propagation/SpanContextParseException.java47
-rw-r--r--api/src/main/java/io/opencensus/trace/propagation/TextFormat.java204
-rw-r--r--api/src/main/java/io/opencensus/trace/samplers/AlwaysSampleSampler.java55
-rw-r--r--api/src/main/java/io/opencensus/trace/samplers/NeverSampleSampler.java55
-rw-r--r--api/src/main/java/io/opencensus/trace/samplers/ProbabilitySampler.java107
-rw-r--r--api/src/main/java/io/opencensus/trace/samplers/Samplers.java65
-rw-r--r--api/src/main/java/io/opencensus/trace/unsafe/ContextUtils.java45
-rw-r--r--api/src/test/java/io/opencensus/common/DurationTest.java152
-rw-r--r--api/src/test/java/io/opencensus/common/FunctionsTest.java64
-rw-r--r--api/src/test/java/io/opencensus/common/ServerStatsEncodingTest.java155
-rw-r--r--api/src/test/java/io/opencensus/common/ServerStatsFieldEnumsTest.java56
-rw-r--r--api/src/test/java/io/opencensus/common/ServerStatsTest.java78
-rw-r--r--api/src/test/java/io/opencensus/common/TimeUtilsTest.java60
-rw-r--r--api/src/test/java/io/opencensus/common/TimestampTest.java217
-rw-r--r--api/src/test/java/io/opencensus/internal/ProviderTest.java114
-rw-r--r--api/src/test/java/io/opencensus/internal/StringUtilsTest.java35
-rw-r--r--api/src/test/java/io/opencensus/internal/UtilsTest.java144
-rw-r--r--api/src/test/java/io/opencensus/metrics/DerivedDoubleGaugeTest.java89
-rw-r--r--api/src/test/java/io/opencensus/metrics/DerivedLongGaugeTest.java89
-rw-r--r--api/src/test/java/io/opencensus/metrics/DoubleGaugeTest.java88
-rw-r--r--api/src/test/java/io/opencensus/metrics/LabelKeyTest.java86
-rw-r--r--api/src/test/java/io/opencensus/metrics/LabelValueTest.java74
-rw-r--r--api/src/test/java/io/opencensus/metrics/LongGaugeTest.java87
-rw-r--r--api/src/test/java/io/opencensus/metrics/MetricRegistryTest.java220
-rw-r--r--api/src/test/java/io/opencensus/metrics/MetricsComponentTest.java40
-rw-r--r--api/src/test/java/io/opencensus/metrics/MetricsTest.java71
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/DistributionTest.java331
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/ExportComponentTest.java33
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/MetricDescriptorTest.java103
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/MetricProducerManagerTest.java80
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/MetricTest.java180
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/PointTest.java69
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/SummaryTest.java189
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/TimeSeriesTest.java151
-rw-r--r--api/src/test/java/io/opencensus/metrics/export/ValueTest.java168
-rw-r--r--api/src/test/java/io/opencensus/stats/AggregationDataTest.java231
-rw-r--r--api/src/test/java/io/opencensus/stats/AggregationTest.java97
-rw-r--r--api/src/test/java/io/opencensus/stats/BucketBoundariesTest.java87
-rw-r--r--api/src/test/java/io/opencensus/stats/MeasureTest.java129
-rw-r--r--api/src/test/java/io/opencensus/stats/NoopStatsTest.java133
-rw-r--r--api/src/test/java/io/opencensus/stats/NoopViewManagerTest.java186
-rw-r--r--api/src/test/java/io/opencensus/stats/StatsTest.java85
-rw-r--r--api/src/test/java/io/opencensus/stats/ViewDataTest.java301
-rw-r--r--api/src/test/java/io/opencensus/stats/ViewTest.java164
-rw-r--r--api/src/test/java/io/opencensus/tags/InternalUtilsTest.java44
-rw-r--r--api/src/test/java/io/opencensus/tags/NoopTagsTest.java179
-rw-r--r--api/src/test/java/io/opencensus/tags/TagContextTest.java103
-rw-r--r--api/src/test/java/io/opencensus/tags/TagKeyTest.java80
-rw-r--r--api/src/test/java/io/opencensus/tags/TagTest.java46
-rw-r--r--api/src/test/java/io/opencensus/tags/TagValueTest.java75
-rw-r--r--api/src/test/java/io/opencensus/tags/TagsTest.java85
-rw-r--r--api/src/test/java/io/opencensus/tags/propagation/TagContextDeserializationExceptionTest.java44
-rw-r--r--api/src/test/java/io/opencensus/tags/propagation/TagContextSerializationExceptionTest.java44
-rw-r--r--api/src/test/java/io/opencensus/tags/unsafe/ContextUtilsTest.java62
-rw-r--r--api/src/test/java/io/opencensus/trace/AnnotationTest.java102
-rw-r--r--api/src/test/java/io/opencensus/trace/AttributeValueTest.java233
-rw-r--r--api/src/test/java/io/opencensus/trace/BlankSpanTest.java68
-rw-r--r--api/src/test/java/io/opencensus/trace/CurrentSpanUtilsTest.java294
-rw-r--r--api/src/test/java/io/opencensus/trace/EndSpanOptionsTest.java80
-rw-r--r--api/src/test/java/io/opencensus/trace/LinkTest.java112
-rw-r--r--api/src/test/java/io/opencensus/trace/LowerCaseBase16EncodingTest.java83
-rw-r--r--api/src/test/java/io/opencensus/trace/MessageEventTest.java84
-rw-r--r--api/src/test/java/io/opencensus/trace/NetworkEventTest.java118
-rw-r--r--api/src/test/java/io/opencensus/trace/NoopSpan.java69
-rw-r--r--api/src/test/java/io/opencensus/trace/SpanBuilderTest.java104
-rw-r--r--api/src/test/java/io/opencensus/trace/SpanContextTest.java133
-rw-r--r--api/src/test/java/io/opencensus/trace/SpanIdTest.java90
-rw-r--r--api/src/test/java/io/opencensus/trace/SpanTest.java119
-rw-r--r--api/src/test/java/io/opencensus/trace/StatusTest.java54
-rw-r--r--api/src/test/java/io/opencensus/trace/TraceComponentTest.java59
-rw-r--r--api/src/test/java/io/opencensus/trace/TraceIdTest.java93
-rw-r--r--api/src/test/java/io/opencensus/trace/TraceOptionsTest.java98
-rw-r--r--api/src/test/java/io/opencensus/trace/TracerTest.java174
-rw-r--r--api/src/test/java/io/opencensus/trace/TracestateTest.java235
-rw-r--r--api/src/test/java/io/opencensus/trace/TracingTest.java83
-rw-r--r--api/src/test/java/io/opencensus/trace/config/TraceConfigTest.java51
-rw-r--r--api/src/test/java/io/opencensus/trace/config/TraceParamsTest.java97
-rw-r--r--api/src/test/java/io/opencensus/trace/export/ExportComponentTest.java46
-rw-r--r--api/src/test/java/io/opencensus/trace/export/NoopRunningSpanStoreTest.java55
-rw-r--r--api/src/test/java/io/opencensus/trace/export/NoopSampledSpanStoreTest.java94
-rw-r--r--api/src/test/java/io/opencensus/trace/export/SpanDataTest.java321
-rw-r--r--api/src/test/java/io/opencensus/trace/internal/BaseMessageEventUtilsTest.java71
-rw-r--r--api/src/test/java/io/opencensus/trace/propagation/BinaryFormatTest.java71
-rw-r--r--api/src/test/java/io/opencensus/trace/propagation/PropagationComponentTest.java36
-rw-r--r--api/src/test/java/io/opencensus/trace/propagation/SpanContextParseExceptionTest.java42
-rw-r--r--api/src/test/java/io/opencensus/trace/propagation/TextFormatTest.java75
-rw-r--r--api/src/test/java/io/opencensus/trace/samplers/SamplersTest.java281
199 files changed, 24340 insertions, 0 deletions
diff --git a/api/README.md b/api/README.md
new file mode 100644
index 00000000..83891d4d
--- /dev/null
+++ b/api/README.md
@@ -0,0 +1,6 @@
+OpenCensus API
+======================================================
+
+* Java 6 and Android compatible.
+* The abstract classes in this directory can be subclassed to create alternative
+ implementations of the OpenCensus library.
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 00000000..31274ca0
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,15 @@
+description = 'OpenCensus API'
+
+dependencies {
+ compile libraries.grpc_context
+
+ compileOnly libraries.auto_value
+
+ signature "org.codehaus.mojo.signature:java17:1.0@signature"
+ signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
+
+javadoc {
+ exclude 'io/opencensus/internal/**'
+ exclude 'io/opencensus/trace/internal/**'
+}
diff --git a/api/src/main/java/io/opencensus/common/Clock.java b/api/src/main/java/io/opencensus/common/Clock.java
new file mode 100644
index 00000000..cd311935
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/Clock.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017, 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.common;
+
+/**
+ * Interface for getting the current time.
+ *
+ * @since 0.5
+ */
+public abstract class Clock {
+
+ /**
+ * Obtains the current instant from this clock.
+ *
+ * @return the current instant.
+ * @since 0.5
+ */
+ public abstract Timestamp now();
+
+ /**
+ * Returns a time measurement with nanosecond precision that can only be used to calculate elapsed
+ * time.
+ *
+ * @return a time measurement with nanosecond precision that can only be used to calculate elapsed
+ * time.
+ * @since 0.5
+ */
+ public abstract long nowNanos();
+}
diff --git a/api/src/main/java/io/opencensus/common/Duration.java b/api/src/main/java/io/opencensus/common/Duration.java
new file mode 100644
index 00000000..f46cd187
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/Duration.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2016-17, 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.common;
+
+import static io.opencensus.common.TimeUtils.MAX_NANOS;
+import static io.opencensus.common.TimeUtils.MAX_SECONDS;
+import static io.opencensus.common.TimeUtils.MILLIS_PER_SECOND;
+import static io.opencensus.common.TimeUtils.NANOS_PER_MILLI;
+
+import com.google.auto.value.AutoValue;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Represents a signed, fixed-length span of time represented as a count of seconds and fractions of
+ * seconds at nanosecond resolution. It is independent of any calendar and concepts like "day" or
+ * "month". Range is approximately +-10,000 years.
+ *
+ * @since 0.5
+ */
+@Immutable
+@AutoValue
+public abstract class Duration implements Comparable<Duration> {
+
+ /**
+ * Creates a new time duration from given seconds and nanoseconds.
+ *
+ * @param seconds Signed seconds of the span of time. Must be from -315,576,000,000 to
+ * +315,576,000,000 inclusive.
+ * @param nanos Signed fractions of a second at nanosecond resolution of the span of time.
+ * Durations less than one second are represented with a 0 `seconds` field and a positive or
+ * negative `nanos` field. For durations of one second or more, a non-zero value for the
+ * `nanos` field must be of the same sign as the `seconds` field. Must be from -999,999,999 to
+ * +999,999,999 inclusive.
+ * @return new {@code Duration} with specified fields.
+ * @throws IllegalArgumentException if the arguments are out of range or have inconsistent sign.
+ * @since 0.5
+ */
+ public static Duration create(long seconds, int nanos) {
+ if (seconds < -MAX_SECONDS) {
+ throw new IllegalArgumentException(
+ "'seconds' is less than minimum (" + -MAX_SECONDS + "): " + seconds);
+ }
+ if (seconds > MAX_SECONDS) {
+ throw new IllegalArgumentException(
+ "'seconds' is greater than maximum (" + MAX_SECONDS + "): " + seconds);
+ }
+ if (nanos < -MAX_NANOS) {
+ throw new IllegalArgumentException(
+ "'nanos' is less than minimum (" + -MAX_NANOS + "): " + nanos);
+ }
+ if (nanos > MAX_NANOS) {
+ throw new IllegalArgumentException(
+ "'nanos' is greater than maximum (" + MAX_NANOS + "): " + nanos);
+ }
+ if ((seconds < 0 && nanos > 0) || (seconds > 0 && nanos < 0)) {
+ throw new IllegalArgumentException(
+ "'seconds' and 'nanos' have inconsistent sign: seconds=" + seconds + ", nanos=" + nanos);
+ }
+ return new AutoValue_Duration(seconds, nanos);
+ }
+
+ /**
+ * Creates a new {@code Duration} from given milliseconds.
+ *
+ * @param millis the duration in milliseconds.
+ * @return a new {@code Duration} from given milliseconds.
+ * @throws IllegalArgumentException if the number of milliseconds is out of the range that can be
+ * represented by {@code Duration}.
+ * @since 0.5
+ */
+ public static Duration fromMillis(long millis) {
+ long seconds = millis / MILLIS_PER_SECOND;
+ int nanos = (int) (millis % MILLIS_PER_SECOND * NANOS_PER_MILLI);
+ return Duration.create(seconds, nanos);
+ }
+
+ /**
+ * Converts a {@link Duration} to milliseconds.
+ *
+ * @return the milliseconds representation of this {@code Duration}.
+ * @since 0.13
+ */
+ public long toMillis() {
+ return TimeUnit.SECONDS.toMillis(getSeconds()) + TimeUnit.NANOSECONDS.toMillis(getNanos());
+ }
+
+ /**
+ * Returns the number of seconds in the {@code Duration}.
+ *
+ * @return the number of seconds in the {@code Duration}.
+ * @since 0.5
+ */
+ public abstract long getSeconds();
+
+ /**
+ * Returns the number of nanoseconds in the {@code Duration}.
+ *
+ * @return the number of nanoseconds in the {@code Duration}.
+ * @since 0.5
+ */
+ public abstract int getNanos();
+
+ /**
+ * Compares this {@code Duration} to the specified {@code Duration}.
+ *
+ * @param otherDuration the other {@code Duration} to compare to, not {@code null}.
+ * @return the comparator value: zero if equal, negative if this duration is smaller than
+ * otherDuration, positive if larger.
+ * @throws NullPointerException if otherDuration is {@code null}.
+ */
+ @Override
+ public int compareTo(Duration otherDuration) {
+ int cmp = TimeUtils.compareLongs(getSeconds(), otherDuration.getSeconds());
+ if (cmp != 0) {
+ return cmp;
+ }
+ return TimeUtils.compareLongs(getNanos(), otherDuration.getNanos());
+ }
+
+ Duration() {}
+}
diff --git a/api/src/main/java/io/opencensus/common/ExperimentalApi.java b/api/src/main/java/io/opencensus/common/ExperimentalApi.java
new file mode 100644
index 00000000..7a4da7c7
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/ExperimentalApi.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017, 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.common;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates a public API that can change at any time, and has no guarantee of API stability and
+ * backward-compatibility.
+ *
+ * <p>Usage guidelines:
+ *
+ * <ol>
+ * <li>This annotation is used only on public API. Internal interfaces should not use it.
+ * <li>After OpenCensus has gained API stability, this annotation can only be added to new API.
+ * Adding it to an existing API is considered API-breaking.
+ * <li>Removing this annotation from an API gives it stable status.
+ * </ol>
+ *
+ * @since 0.8
+ */
+@Internal
+@Retention(RetentionPolicy.SOURCE)
+@Target({
+ ElementType.ANNOTATION_TYPE,
+ ElementType.CONSTRUCTOR,
+ ElementType.FIELD,
+ ElementType.METHOD,
+ ElementType.PACKAGE,
+ ElementType.TYPE
+})
+@Documented
+public @interface ExperimentalApi {
+ /**
+ * Context information such as links to discussion thread, tracking issue etc.
+ *
+ * @since 0.8
+ */
+ String value() default "";
+}
diff --git a/api/src/main/java/io/opencensus/common/Function.java b/api/src/main/java/io/opencensus/common/Function.java
new file mode 100644
index 00000000..a9ed5a9e
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/Function.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-17, 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.common;
+
+/**
+ * Used to specify matching functions for use encoding tagged unions (i.e. sum types) in Java. See
+ * {@link io.opencensus.trace.AttributeValue#match} for an example of its use.
+ *
+ * <p>Note: This class is based on the java.util.Function class added in Java 1.8. We cannot use the
+ * Function from Java 1.8 because this library is Java 1.6 compatible.
+ *
+ * @since 0.5
+ */
+public interface Function<A, B> {
+
+ /**
+ * Applies the function to the given argument.
+ *
+ * @param arg the argument to the function.
+ * @return the result of the function.
+ * @since 0.5
+ */
+ B apply(A arg);
+}
diff --git a/api/src/main/java/io/opencensus/common/Functions.java b/api/src/main/java/io/opencensus/common/Functions.java
new file mode 100644
index 00000000..ea3457ca
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/Functions.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017, 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.common;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Commonly used {@link Function} instances.
+ *
+ * @since 0.5
+ */
+public final class Functions {
+ private Functions() {}
+
+ private static final Function<Object, /*@Nullable*/ Void> RETURN_NULL =
+ new Function<Object, /*@Nullable*/ Void>() {
+ @Override
+ @javax.annotation.Nullable
+ public Void apply(Object ignored) {
+ return null;
+ }
+ };
+
+ private static final Function<Object, Void> THROW_ILLEGAL_ARGUMENT_EXCEPTION =
+ new Function<Object, Void>() {
+ @Override
+ public Void apply(Object ignored) {
+ throw new IllegalArgumentException();
+ }
+ };
+
+ private static final Function<Object, Void> THROW_ASSERTION_ERROR =
+ new Function<Object, Void>() {
+ @Override
+ public Void apply(Object ignored) {
+ throw new AssertionError();
+ }
+ };
+
+ private static final Function<Object, /*@Nullable*/ String> RETURN_TO_STRING =
+ new Function<Object, /*@Nullable*/ String>() {
+ @Override
+ public /*@Nullable*/ String apply(Object input) {
+ return input == null ? null : input.toString();
+ }
+ };
+
+ /**
+ * A {@code Function} that always ignores its argument and returns {@code null}.
+ *
+ * @return a {@code Function} that always ignores its argument and returns {@code null}.
+ * @since 0.5
+ */
+ public static <T> Function<Object, /*@Nullable*/ T> returnNull() {
+ // It is safe to cast a producer of Void to anything, because Void is always null.
+ @SuppressWarnings("unchecked")
+ Function<Object, /*@Nullable*/ T> function = (Function<Object, /*@Nullable*/ T>) RETURN_NULL;
+ return function;
+ }
+
+ /**
+ * A {@code Function} that always ignores its argument and returns a constant value.
+ *
+ * @return a {@code Function} that always ignores its argument and returns a constant value.
+ * @since 0.5
+ */
+ public static <T> Function<Object, T> returnConstant(final T constant) {
+ return new Function<Object, T>() {
+ @Override
+ public T apply(Object ignored) {
+ return constant;
+ }
+ };
+ }
+
+ /**
+ * A {@code Function} that always returns the {@link #toString()} value of the input.
+ *
+ * @return a {@code Function} that always returns the {@link #toString()} value of the input.
+ * @since 0.17
+ */
+ public static Function<Object, /*@Nullable*/ String> returnToString() {
+ return RETURN_TO_STRING;
+ }
+
+ /**
+ * A {@code Function} that always ignores its argument and throws an {@link
+ * IllegalArgumentException}.
+ *
+ * @return a {@code Function} that always ignores its argument and throws an {@link
+ * IllegalArgumentException}.
+ * @since 0.5
+ */
+ public static <T> Function<Object, T> throwIllegalArgumentException() {
+ // It is safe to cast this function to have any return type, since it never returns a result.
+ @SuppressWarnings("unchecked")
+ Function<Object, T> function = (Function<Object, T>) THROW_ILLEGAL_ARGUMENT_EXCEPTION;
+ return function;
+ }
+
+ /**
+ * A {@code Function} that always ignores its argument and throws an {@link AssertionError}.
+ *
+ * @return a {@code Function} that always ignores its argument and throws an {@code
+ * AssertionError}.
+ * @since 0.6
+ */
+ public static <T> Function<Object, T> throwAssertionError() {
+ // It is safe to cast this function to have any return type, since it never returns a result.
+ @SuppressWarnings("unchecked")
+ Function<Object, T> function = (Function<Object, T>) THROW_ASSERTION_ERROR;
+ return function;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/common/Internal.java b/api/src/main/java/io/opencensus/common/Internal.java
new file mode 100644
index 00000000..d84fba20
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/Internal.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017, 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.common;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a program element (class, method, package etc) which is internal to OpenCensus, not
+ * part of the public API, and should not be used by users of the OpenCensus library.
+ *
+ * @since 0.5
+ */
+@Internal
+@Retention(RetentionPolicy.SOURCE)
+@Target({
+ ElementType.ANNOTATION_TYPE,
+ ElementType.CONSTRUCTOR,
+ ElementType.FIELD,
+ ElementType.METHOD,
+ ElementType.PACKAGE,
+ ElementType.TYPE
+})
+@Documented
+public @interface Internal {}
diff --git a/api/src/main/java/io/opencensus/common/NonThrowingCloseable.java b/api/src/main/java/io/opencensus/common/NonThrowingCloseable.java
new file mode 100644
index 00000000..30d07ac7
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/NonThrowingCloseable.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017, 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.common;
+
+import java.io.Closeable;
+
+/**
+ * An {@link Closeable} which cannot throw a checked exception.
+ *
+ * <p>This is useful because such a reversion otherwise requires the caller to catch the
+ * (impossible) Exception in the try-with-resources.
+ *
+ * <p>Example of usage:
+ *
+ * <pre>
+ * try (NonThrowingAutoCloseable ctx = tryEnter()) {
+ * ...
+ * }
+ * </pre>
+ *
+ * @deprecated {@link Scope} is a better match for operations involving the current context.
+ * @since 0.5
+ */
+@Deprecated
+public interface NonThrowingCloseable extends Closeable {
+ @Override
+ void close();
+}
diff --git a/api/src/main/java/io/opencensus/common/OpenCensusLibraryInformation.java b/api/src/main/java/io/opencensus/common/OpenCensusLibraryInformation.java
new file mode 100644
index 00000000..3f659c12
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/OpenCensusLibraryInformation.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-17, 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.common;
+
+/**
+ * Class holder for all common constants (such as the version) for the OpenCensus Java library.
+ *
+ * @since 0.8
+ */
+@ExperimentalApi
+public final class OpenCensusLibraryInformation {
+
+ /**
+ * The current version of the OpenCensus Java library.
+ *
+ * @since 0.8
+ */
+ public static final String VERSION = "0.17.0-SNAPSHOT"; // CURRENT_OPENCENSUS_VERSION
+
+ private OpenCensusLibraryInformation() {}
+}
diff --git a/api/src/main/java/io/opencensus/common/Scope.java b/api/src/main/java/io/opencensus/common/Scope.java
new file mode 100644
index 00000000..de954f50
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/Scope.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017, 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.common;
+
+/**
+ * A {@link java.io.Closeable} that represents a change to the current context over a scope of code.
+ * {@link Scope#close} cannot throw a checked exception.
+ *
+ * <p>Example of usage:
+ *
+ * <pre>
+ * try (Scope ctx = tryEnter()) {
+ * ...
+ * }
+ * </pre>
+ *
+ * @since 0.6
+ */
+@SuppressWarnings("deprecation")
+public interface Scope extends NonThrowingCloseable {
+ @Override
+ void close();
+}
diff --git a/api/src/main/java/io/opencensus/common/ServerStats.java b/api/src/main/java/io/opencensus/common/ServerStats.java
new file mode 100644
index 00000000..42efa1f2
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/ServerStats.java
@@ -0,0 +1,86 @@
+/*
+ * 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.common;
+
+import com.google.auto.value.AutoValue;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A representation of stats measured on the server side.
+ *
+ * @since 0.16
+ */
+@Immutable
+@AutoValue
+public abstract class ServerStats {
+
+ ServerStats() {}
+
+ /**
+ * Returns Load Balancer latency, a latency observed at Load Balancer.
+ *
+ * @return Load Balancer latency in nanoseconds.
+ * @since 0.16
+ */
+ public abstract long getLbLatencyNs();
+
+ /**
+ * Returns Service latency, a latency observed at Server.
+ *
+ * @return Service latency in nanoseconds.
+ * @since 0.16
+ */
+ public abstract long getServiceLatencyNs();
+
+ /**
+ * Returns Trace options, a set of bits indicating properties of trace.
+ *
+ * @return Trace options a set of bits indicating properties of trace.
+ * @since 0.16
+ */
+ public abstract byte getTraceOption();
+
+ /**
+ * Creates new {@link ServerStats} from specified parameters.
+ *
+ * @param lbLatencyNs Represents request processing latency observed on Load Balancer. It is
+ * measured in nanoseconds. Must not be less than 0. Value of 0 represents that the latency is
+ * not measured.
+ * @param serviceLatencyNs Represents request processing latency observed on Server. It is
+ * measured in nanoseconds. Must not be less than 0. Value of 0 represents that the latency is
+ * not measured.
+ * @param traceOption Represents set of bits to indicate properties of trace. Currently it used
+ * only the least signification bit to represent sampling of the request on the server side.
+ * Other bits are ignored.
+ * @return new {@code ServerStats} with specified fields.
+ * @throws IllegalArgumentException if the arguments are out of range.
+ * @since 0.16
+ */
+ public static ServerStats create(long lbLatencyNs, long serviceLatencyNs, byte traceOption) {
+
+ if (lbLatencyNs < 0) {
+ throw new IllegalArgumentException("'getLbLatencyNs' is less than zero: " + lbLatencyNs);
+ }
+
+ if (serviceLatencyNs < 0) {
+ throw new IllegalArgumentException(
+ "'getServiceLatencyNs' is less than zero: " + serviceLatencyNs);
+ }
+
+ return new AutoValue_ServerStats(lbLatencyNs, serviceLatencyNs, traceOption);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/common/ServerStatsDeserializationException.java b/api/src/main/java/io/opencensus/common/ServerStatsDeserializationException.java
new file mode 100644
index 00000000..2332733c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/ServerStatsDeserializationException.java
@@ -0,0 +1,47 @@
+/*
+ * 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.common;
+
+/**
+ * Exception thrown when a {@link ServerStats} cannot be parsed.
+ *
+ * @since 0.16
+ */
+public final class ServerStatsDeserializationException extends Exception {
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructs a new {@code ServerStatsDeserializationException} with the given message.
+ *
+ * @param message a message describing the error.
+ * @since 0.16
+ */
+ public ServerStatsDeserializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code ServerStatsDeserializationException} with the given message and cause.
+ *
+ * @param message a message describing the error.
+ * @param cause the cause of the error.
+ * @since 0.16
+ */
+ public ServerStatsDeserializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/common/ServerStatsEncoding.java b/api/src/main/java/io/opencensus/common/ServerStatsEncoding.java
new file mode 100644
index 00000000..024a93f8
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/ServerStatsEncoding.java
@@ -0,0 +1,125 @@
+/*
+ * 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.common;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A service class to encode/decode {@link ServerStats} as defined by the spec.
+ *
+ * <p>See <a
+ * href="https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/CensusServerStatsEncoding.md">opencensus-server-stats-specs</a>
+ * for encoding {@code ServerStats}
+ *
+ * <p>Use {@code ServerStatsEncoding.toBytes(ServerStats stats)} to encode.
+ *
+ * <p>Use {@code ServerStatsEncoding.parseBytes(byte[] serialized)} to decode.
+ *
+ * @since 0.16
+ */
+public final class ServerStatsEncoding {
+
+ private ServerStatsEncoding() {}
+
+ /**
+ * The current encoding version. The value is {@value #CURRENT_VERSION}
+ *
+ * @since 0.16
+ */
+ public static final byte CURRENT_VERSION = (byte) 0;
+
+ /**
+ * Encodes the {@link ServerStats} as per the Opencensus Summary Span specification.
+ *
+ * @param stats {@code ServerStats} to encode.
+ * @return encoded byte array.
+ * @since 0.16
+ */
+ public static byte[] toBytes(ServerStats stats) {
+ // Should this be optimized to not include invalid values?
+
+ ByteBuffer bb = ByteBuffer.allocate(ServerStatsFieldEnums.getTotalSize() + 1);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+
+ // put version
+ bb.put(CURRENT_VERSION);
+
+ bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_LB_LATENCY_ID.value());
+ bb.putLong(stats.getLbLatencyNs());
+
+ bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_SERVICE_LATENCY_ID.value());
+ bb.putLong(stats.getServiceLatencyNs());
+
+ bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_TRACE_OPTION_ID.value());
+ bb.put(stats.getTraceOption());
+ return bb.array();
+ }
+
+ /**
+ * Decodes serialized byte array to create {@link ServerStats} as per Opencensus Summary Span
+ * specification.
+ *
+ * @param serialized encoded {@code ServerStats} in byte array.
+ * @return decoded {@code ServerStats}. null if decoding fails.
+ * @since 0.16
+ */
+ public static ServerStats parseBytes(byte[] serialized)
+ throws ServerStatsDeserializationException {
+ final ByteBuffer bb = ByteBuffer.wrap(serialized);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ long serviceLatencyNs = 0L;
+ long lbLatencyNs = 0L;
+ byte traceOption = (byte) 0;
+
+ // Check the version first.
+ if (!bb.hasRemaining()) {
+ throw new ServerStatsDeserializationException("Serialized ServerStats buffer is empty");
+ }
+ byte version = bb.get();
+
+ if (version > CURRENT_VERSION || version < 0) {
+ throw new ServerStatsDeserializationException("Invalid ServerStats version: " + version);
+ }
+
+ while (bb.hasRemaining()) {
+ ServerStatsFieldEnums.Id id = ServerStatsFieldEnums.Id.valueOf((int) bb.get() & 0xFF);
+ if (id == null) {
+ // Skip remaining;
+ bb.position(bb.limit());
+ } else {
+ switch (id) {
+ case SERVER_STATS_LB_LATENCY_ID:
+ lbLatencyNs = bb.getLong();
+ break;
+ case SERVER_STATS_SERVICE_LATENCY_ID:
+ serviceLatencyNs = bb.getLong();
+ break;
+ case SERVER_STATS_TRACE_OPTION_ID:
+ traceOption = bb.get();
+ break;
+ }
+ }
+ }
+ try {
+ return ServerStats.create(lbLatencyNs, serviceLatencyNs, traceOption);
+ } catch (IllegalArgumentException e) {
+ throw new ServerStatsDeserializationException(
+ "Serialized ServiceStats contains invalid values: " + e.getMessage());
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/common/ServerStatsFieldEnums.java b/api/src/main/java/io/opencensus/common/ServerStatsFieldEnums.java
new file mode 100644
index 00000000..ff3cfda9
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/ServerStatsFieldEnums.java
@@ -0,0 +1,159 @@
+/*
+ * 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.common;
+
+import java.util.TreeMap;
+import javax.annotation.Nullable;
+
+/**
+ * A Enum representation for Ids and Size for attributes of {@code ServerStats}.
+ *
+ * <p>See <a
+ * href="https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/CensusServerStatsEncoding.md">opencensus-server-stats-specs</a>
+ * for the field ids and their length defined for Server Stats
+ *
+ * @since 0.16
+ */
+public final class ServerStatsFieldEnums {
+
+ /**
+ * Available Ids for {@code ServerStats} attributes.
+ *
+ * @since 0.16
+ */
+ public enum Id {
+ /**
+ * Id for Latency observed at Load Balancer.
+ *
+ * @since 0.16
+ */
+ SERVER_STATS_LB_LATENCY_ID(0),
+ /**
+ * Id for Latency observed at Server.
+ *
+ * @since 0.16
+ */
+ SERVER_STATS_SERVICE_LATENCY_ID(1),
+ /**
+ * Id for Trace options.
+ *
+ * @since 0.16
+ */
+ SERVER_STATS_TRACE_OPTION_ID(2);
+
+ private final int value;
+
+ private Id(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the numerical value of the {@link Id}.
+ *
+ * @return the numerical value of the {@code Id}.
+ * @since 0.16
+ */
+ public int value() {
+ return value;
+ }
+
+ private static final TreeMap<Integer, Id> map = new TreeMap<Integer, Id>();
+
+ static {
+ for (Id id : Id.values()) {
+ map.put(id.value, id);
+ }
+ }
+
+ /**
+ * Returns the {@link Id} representing the value value of the id.
+ *
+ * @param value integer value for which {@code Id} is being requested.
+ * @return the numerical value of the id. null if the id is not valid
+ * @since 0.16
+ */
+ @Nullable
+ public static Id valueOf(int value) {
+ return map.get(value);
+ }
+ }
+
+ /**
+ * Size for each attributes in {@code ServerStats}.
+ *
+ * @since 0.16
+ */
+ public enum Size {
+ /**
+ * Number of bytes used to represent latency observed at Load Balancer.
+ *
+ * @since 0.16
+ */
+ SERVER_STATS_LB_LATENCY_SIZE(8),
+ /**
+ * Number of bytes used to represent latency observed at Server.
+ *
+ * @since 0.16
+ */
+ SERVER_STATS_SERVICE_LATENCY_SIZE(8),
+ /**
+ * Number of bytes used to represent Trace option.
+ *
+ * @since 0.16
+ */
+ SERVER_STATS_TRACE_OPTION_SIZE(1);
+
+ private final int value;
+
+ private Size(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the numerical value of the {@link Size}.
+ *
+ * @return the numerical value of the {@code Size}.
+ * @since 0.16
+ */
+ public int value() {
+ return value;
+ }
+ }
+
+ private static final int TOTALSIZE = computeTotalSize();
+
+ private ServerStatsFieldEnums() {}
+
+ private static int computeTotalSize() {
+ int sum = 0;
+ for (Size sizeValue : Size.values()) {
+ sum += sizeValue.value();
+ sum += 1; // For Id
+ }
+ return sum;
+ }
+
+ /**
+ * Returns the total size required to encode the {@code ServerStats}.
+ *
+ * @return the total size required to encode all fields in {@code ServerStats}.
+ * @since 0.16
+ */
+ public static int getTotalSize() {
+ return TOTALSIZE;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/common/TimeUtils.java b/api/src/main/java/io/opencensus/common/TimeUtils.java
new file mode 100644
index 00000000..db119e2e
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/TimeUtils.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017, 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.common;
+
+import java.math.BigInteger;
+
+/** Util class for {@link Timestamp} and {@link Duration}. */
+final class TimeUtils {
+ static final long MAX_SECONDS = 315576000000L;
+ static final int MAX_NANOS = 999999999;
+ static final long MILLIS_PER_SECOND = 1000L;
+ static final long NANOS_PER_MILLI = 1000 * 1000;
+ static final long NANOS_PER_SECOND = NANOS_PER_MILLI * MILLIS_PER_SECOND;
+
+ private TimeUtils() {}
+
+ /**
+ * Compares two longs. This functionality is provided by {@code Long.compare(long, long)} in Java
+ * 7.
+ */
+ static int compareLongs(long x, long y) {
+ if (x < y) {
+ return -1;
+ } else if (x == y) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ private static final BigInteger MAX_LONG_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
+ private static final BigInteger MIN_LONG_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
+
+ /**
+ * Adds two longs and throws an {@link ArithmeticException} if the result overflows. This
+ * functionality is provided by {@code Math.addExact(long, long)} in Java 8.
+ */
+ static long checkedAdd(long x, long y) {
+ BigInteger sum = BigInteger.valueOf(x).add(BigInteger.valueOf(y));
+ if (sum.compareTo(MAX_LONG_VALUE) > 0 || sum.compareTo(MIN_LONG_VALUE) < 0) {
+ throw new ArithmeticException("Long sum overflow: x=" + x + ", y=" + y);
+ }
+ return x + y;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/common/Timestamp.java b/api/src/main/java/io/opencensus/common/Timestamp.java
new file mode 100644
index 00000000..d17b3fd8
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/Timestamp.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2016-17, 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.common;
+
+import static io.opencensus.common.TimeUtils.MAX_NANOS;
+import static io.opencensus.common.TimeUtils.MAX_SECONDS;
+import static io.opencensus.common.TimeUtils.MILLIS_PER_SECOND;
+import static io.opencensus.common.TimeUtils.NANOS_PER_MILLI;
+import static io.opencensus.common.TimeUtils.NANOS_PER_SECOND;
+
+import com.google.auto.value.AutoValue;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A representation of an instant in time. The instant is the number of nanoseconds after the number
+ * of seconds since the Unix Epoch.
+ *
+ * <p>Use {@code Tracing.getClock().now()} to get the current timestamp since epoch
+ * (1970-01-01T00:00:00Z).
+ *
+ * @since 0.5
+ */
+@Immutable
+@AutoValue
+public abstract class Timestamp implements Comparable<Timestamp> {
+
+ Timestamp() {}
+
+ /**
+ * Creates a new timestamp from given seconds and nanoseconds.
+ *
+ * @param seconds Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be
+ * from from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.
+ * @param nanos Non-negative fractions of a second at nanosecond resolution. Negative second
+ * values with fractions must still have non-negative nanos values that count forward in time.
+ * Must be from 0 to 999,999,999 inclusive.
+ * @return new {@code Timestamp} with specified fields.
+ * @throws IllegalArgumentException if the arguments are out of range.
+ * @since 0.5
+ */
+ public static Timestamp create(long seconds, int nanos) {
+ if (seconds < -MAX_SECONDS) {
+ throw new IllegalArgumentException(
+ "'seconds' is less than minimum (" + -MAX_SECONDS + "): " + seconds);
+ }
+ if (seconds > MAX_SECONDS) {
+ throw new IllegalArgumentException(
+ "'seconds' is greater than maximum (" + MAX_SECONDS + "): " + seconds);
+ }
+ if (nanos < 0) {
+ throw new IllegalArgumentException("'nanos' is less than zero: " + nanos);
+ }
+ if (nanos > MAX_NANOS) {
+ throw new IllegalArgumentException(
+ "'nanos' is greater than maximum (" + MAX_NANOS + "): " + nanos);
+ }
+ return new AutoValue_Timestamp(seconds, nanos);
+ }
+
+ /**
+ * Creates a new timestamp from the given milliseconds.
+ *
+ * @param epochMilli the timestamp represented in milliseconds since epoch.
+ * @return new {@code Timestamp} with specified fields.
+ * @throws IllegalArgumentException if the number of milliseconds is out of the range that can be
+ * represented by {@code Timestamp}.
+ * @since 0.5
+ */
+ public static Timestamp fromMillis(long epochMilli) {
+ long secs = floorDiv(epochMilli, MILLIS_PER_SECOND);
+ int mos = (int) floorMod(epochMilli, MILLIS_PER_SECOND);
+ return create(secs, (int) (mos * NANOS_PER_MILLI)); // Safe int * NANOS_PER_MILLI
+ }
+
+ /**
+ * Returns the number of seconds since the Unix Epoch represented by this timestamp.
+ *
+ * @return the number of seconds since the Unix Epoch.
+ * @since 0.5
+ */
+ public abstract long getSeconds();
+
+ /**
+ * Returns the number of nanoseconds after the number of seconds since the Unix Epoch represented
+ * by this timestamp.
+ *
+ * @return the number of nanoseconds after the number of seconds since the Unix Epoch.
+ * @since 0.5
+ */
+ public abstract int getNanos();
+
+ /**
+ * Returns a {@code Timestamp} calculated as this {@code Timestamp} plus some number of
+ * nanoseconds.
+ *
+ * @param nanosToAdd the nanos to add, positive or negative.
+ * @return the calculated {@code Timestamp}. For invalid inputs, a {@code Timestamp} of zero is
+ * returned.
+ * @throws ArithmeticException if numeric overflow occurs.
+ * @since 0.5
+ */
+ public Timestamp addNanos(long nanosToAdd) {
+ return plus(0, nanosToAdd);
+ }
+
+ /**
+ * Returns a {@code Timestamp} calculated as this {@code Timestamp} plus some {@code Duration}.
+ *
+ * @param duration the {@code Duration} to add.
+ * @return a {@code Timestamp} with the specified {@code Duration} added.
+ * @since 0.5
+ */
+ public Timestamp addDuration(Duration duration) {
+ return plus(duration.getSeconds(), duration.getNanos());
+ }
+
+ /**
+ * Returns a {@link Duration} calculated as: {@code this - timestamp}.
+ *
+ * @param timestamp the {@code Timestamp} to subtract.
+ * @return the calculated {@code Duration}. For invalid inputs, a {@code Duration} of zero is
+ * returned.
+ * @since 0.5
+ */
+ public Duration subtractTimestamp(Timestamp timestamp) {
+ long durationSeconds = getSeconds() - timestamp.getSeconds();
+ int durationNanos = getNanos() - timestamp.getNanos();
+ if (durationSeconds < 0 && durationNanos > 0) {
+ durationSeconds += 1;
+ durationNanos = (int) (durationNanos - NANOS_PER_SECOND);
+ } else if (durationSeconds > 0 && durationNanos < 0) {
+ durationSeconds -= 1;
+ durationNanos = (int) (durationNanos + NANOS_PER_SECOND);
+ }
+ return Duration.create(durationSeconds, durationNanos);
+ }
+
+ /**
+ * Compares this {@code Timestamp} to the specified {@code Timestamp}.
+ *
+ * @param otherTimestamp the other {@code Timestamp} to compare to, not {@code null}.
+ * @return the comparator value: zero if equal, negative if this timestamp happens before
+ * otherTimestamp, positive if after.
+ * @throws NullPointerException if otherTimestamp is {@code null}.
+ */
+ @Override
+ public int compareTo(Timestamp otherTimestamp) {
+ int cmp = TimeUtils.compareLongs(getSeconds(), otherTimestamp.getSeconds());
+ if (cmp != 0) {
+ return cmp;
+ }
+ return TimeUtils.compareLongs(getNanos(), otherTimestamp.getNanos());
+ }
+
+ // Returns a Timestamp with the specified duration added.
+ private Timestamp plus(long secondsToAdd, long nanosToAdd) {
+ if ((secondsToAdd | nanosToAdd) == 0) {
+ return this;
+ }
+ long epochSec = TimeUtils.checkedAdd(getSeconds(), secondsToAdd);
+ epochSec = TimeUtils.checkedAdd(epochSec, nanosToAdd / NANOS_PER_SECOND);
+ nanosToAdd = nanosToAdd % NANOS_PER_SECOND;
+ long nanoAdjustment = getNanos() + nanosToAdd; // safe int + NANOS_PER_SECOND
+ return ofEpochSecond(epochSec, nanoAdjustment);
+ }
+
+ // Returns a Timestamp calculated using seconds from the epoch and nanosecond fraction of
+ // second (arbitrary number of nanoseconds).
+ private static Timestamp ofEpochSecond(long epochSecond, long nanoAdjustment) {
+ long secs = TimeUtils.checkedAdd(epochSecond, floorDiv(nanoAdjustment, NANOS_PER_SECOND));
+ int nos = (int) floorMod(nanoAdjustment, NANOS_PER_SECOND);
+ return create(secs, nos);
+ }
+
+ // Returns the result of dividing x by y rounded using floor.
+ private static long floorDiv(long x, long y) {
+ return BigDecimal.valueOf(x).divide(BigDecimal.valueOf(y), 0, RoundingMode.FLOOR).longValue();
+ }
+
+ // Returns the floor modulus "x - (floorDiv(x, y) * y)"
+ private static long floorMod(long x, long y) {
+ return x - floorDiv(x, y) * y;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/common/ToDoubleFunction.java b/api/src/main/java/io/opencensus/common/ToDoubleFunction.java
new file mode 100644
index 00000000..6ace2f7c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/ToDoubleFunction.java
@@ -0,0 +1,41 @@
+/*
+ * 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.common;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Represents a function that produces a double-valued result. See {@link
+ * io.opencensus.metrics.MetricRegistry} for an example of its use.
+ *
+ * <p>Note: This class is based on the java.util.ToDoubleFunction class added in Java 1.8. We cannot
+ * use the Function from Java 1.8 because this library is Java 1.6 compatible.
+ *
+ * @since 0.16
+ */
+public interface ToDoubleFunction</*@Nullable*/ T> {
+
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument.
+ * @return the function result.
+ */
+ double applyAsDouble(/*@Nullable*/ T value);
+}
diff --git a/api/src/main/java/io/opencensus/common/ToLongFunction.java b/api/src/main/java/io/opencensus/common/ToLongFunction.java
new file mode 100644
index 00000000..cd2b68ed
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/ToLongFunction.java
@@ -0,0 +1,40 @@
+/*
+ * 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.common;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Represents a function that produces a long-valued result. See {@link
+ * io.opencensus.metrics.MetricRegistry} for an example of its use.
+ *
+ * <p>Note: This class is based on the java.util.ToLongFunction class added in Java 1.8. We cannot
+ * use the Function from Java 1.8 because this library is Java 1.6 compatible.
+ *
+ * @since 0.16
+ */
+public interface ToLongFunction</*@Nullable*/ T> {
+ /**
+ * Applies this function to the given argument.
+ *
+ * @param value the function argument.
+ * @return the function result.
+ */
+ long applyAsLong(/*@Nullable*/ T value);
+}
diff --git a/api/src/main/java/io/opencensus/common/package-info.java b/api/src/main/java/io/opencensus/common/package-info.java
new file mode 100644
index 00000000..1ebfd7cf
--- /dev/null
+++ b/api/src/main/java/io/opencensus/common/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/** Common API between different packages in this artifact. */
+package io.opencensus.common;
diff --git a/api/src/main/java/io/opencensus/internal/DefaultVisibilityForTesting.java b/api/src/main/java/io/opencensus/internal/DefaultVisibilityForTesting.java
new file mode 100644
index 00000000..e90a6573
--- /dev/null
+++ b/api/src/main/java/io/opencensus/internal/DefaultVisibilityForTesting.java
@@ -0,0 +1,37 @@
+/*
+ * 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.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that an element is package-private instead of private only for the purpose of testing.
+ * This annotation is only meant to be used as documentation in the source code.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({
+ ElementType.ANNOTATION_TYPE,
+ ElementType.CONSTRUCTOR,
+ ElementType.FIELD,
+ ElementType.METHOD,
+ ElementType.PACKAGE,
+ ElementType.TYPE
+})
+public @interface DefaultVisibilityForTesting {}
diff --git a/api/src/main/java/io/opencensus/internal/NoopScope.java b/api/src/main/java/io/opencensus/internal/NoopScope.java
new file mode 100644
index 00000000..f4a8da07
--- /dev/null
+++ b/api/src/main/java/io/opencensus/internal/NoopScope.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017, 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.internal;
+
+import io.opencensus.common.Scope;
+
+/** A {@link Scope} that does nothing when it is created or closed. */
+public final class NoopScope implements Scope {
+ private static final Scope INSTANCE = new NoopScope();
+
+ private NoopScope() {}
+
+ /**
+ * Returns a {@code NoopScope}.
+ *
+ * @return a {@code NoopScope}.
+ */
+ public static Scope getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public void close() {}
+}
diff --git a/api/src/main/java/io/opencensus/internal/Provider.java b/api/src/main/java/io/opencensus/internal/Provider.java
new file mode 100644
index 00000000..8cfb7294
--- /dev/null
+++ b/api/src/main/java/io/opencensus/internal/Provider.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016-17, 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.internal;
+
+import java.util.ServiceConfigurationError;
+
+/**
+ * OpenCensus service provider mechanism.
+ *
+ * <pre>{@code
+ * // Initialize a variable using reflection.
+ * foo = Provider.createInstance(
+ * Class.forName("FooImpl", true, classLoader), Foo.class);
+ * }</pre>
+ */
+public final class Provider {
+ private Provider() {}
+
+ /**
+ * Tries to create an instance of the given rawClass as a subclass of the given superclass.
+ *
+ * @param rawClass The class that is initialized.
+ * @param superclass The initialized class must be a subclass of this.
+ * @return an instance of the class given rawClass which is a subclass of the given superclass.
+ * @throws ServiceConfigurationError if any error happens.
+ */
+ public static <T> T createInstance(Class<?> rawClass, Class<T> superclass) {
+ try {
+ return rawClass.asSubclass(superclass).getConstructor().newInstance();
+ } catch (Exception e) {
+ throw new ServiceConfigurationError(
+ "Provider " + rawClass.getName() + " could not be instantiated.", e);
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/internal/StringUtils.java b/api/src/main/java/io/opencensus/internal/StringUtils.java
new file mode 100644
index 00000000..717e333c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/internal/StringUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016-17, 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.internal;
+
+/** Internal utility methods for working with tag keys, tag values, and metric names. */
+public final class StringUtils {
+
+ /**
+ * Determines whether the {@code String} contains only printable characters.
+ *
+ * @param str the {@code String} to be validated.
+ * @return whether the {@code String} contains only printable characters.
+ */
+ public static boolean isPrintableString(String str) {
+ for (int i = 0; i < str.length(); i++) {
+ if (!isPrintableChar(str.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isPrintableChar(char ch) {
+ return ch >= ' ' && ch <= '~';
+ }
+
+ private StringUtils() {}
+}
diff --git a/api/src/main/java/io/opencensus/internal/Utils.java b/api/src/main/java/io/opencensus/internal/Utils.java
new file mode 100644
index 00000000..df5c9840
--- /dev/null
+++ b/api/src/main/java/io/opencensus/internal/Utils.java
@@ -0,0 +1,191 @@
+/*
+ * 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.internal;
+
+import java.util.List;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.NonNull;
+*/
+
+/** General internal utility methods. */
+public final class Utils {
+
+ private Utils() {}
+
+ /**
+ * Throws an {@link IllegalArgumentException} if the argument is false. This method is similar to
+ * {@code Preconditions.checkArgument(boolean, Object)} from Guava.
+ *
+ * @param isValid whether the argument check passed.
+ * @param errorMessage the message to use for the exception. Will be converted to a string using
+ * {@link String#valueOf(Object)}.
+ */
+ public static void checkArgument(
+ boolean isValid, @javax.annotation.Nullable Object errorMessage) {
+ if (!isValid) {
+ throw new IllegalArgumentException(String.valueOf(errorMessage));
+ }
+ }
+
+ /**
+ * Throws an {@link IllegalArgumentException} if the argument is false. This method is similar to
+ * {@code Preconditions.checkArgument(boolean, Object)} from Guava.
+ *
+ * @param expression a boolean expression
+ * @param errorMessageTemplate a template for the exception message should the check fail. The
+ * message is formed by replacing each {@code %s} placeholder in the template with an
+ * argument. These are matched by position - the first {@code %s} gets {@code
+ * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in
+ * square braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
+ * are converted to strings using {@link String#valueOf(Object)}.
+ * @throws IllegalArgumentException if {@code expression} is false
+ * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or
+ * {@code errorMessageArgs} is null (don't let this happen)
+ */
+ public static void checkArgument(
+ boolean expression,
+ String errorMessageTemplate,
+ @javax.annotation.Nullable Object... errorMessageArgs) {
+ if (!expression) {
+ throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ /**
+ * Throws an {@link IllegalStateException} if the argument is false. This method is similar to
+ * {@code Preconditions.checkState(boolean, Object)} from Guava.
+ *
+ * @param isValid whether the state check passed.
+ * @param errorMessage the message to use for the exception. Will be converted to a string using
+ * {@link String#valueOf(Object)}.
+ */
+ public static void checkState(boolean isValid, @javax.annotation.Nullable Object errorMessage) {
+ if (!isValid) {
+ throw new IllegalStateException(String.valueOf(errorMessage));
+ }
+ }
+
+ /**
+ * Validates an index in an array or other container. This method throws an {@link
+ * IllegalArgumentException} if the size is negative and throws an {@link
+ * IndexOutOfBoundsException} if the index is negative or greater than or equal to the size. This
+ * method is similar to {@code Preconditions.checkElementIndex(int, int)} from Guava.
+ *
+ * @param index the index to validate.
+ * @param size the size of the array or container.
+ */
+ public static void checkIndex(int index, int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("Negative size: " + size);
+ }
+ if (index < 0 || index >= size) {
+ throw new IndexOutOfBoundsException("Index out of bounds: size=" + size + ", index=" + index);
+ }
+ }
+
+ /**
+ * Throws a {@link NullPointerException} if the argument is null. This method is similar to {@code
+ * Preconditions.checkNotNull(Object, Object)} from Guava.
+ *
+ * @param arg the argument to check for null.
+ * @param errorMessage the message to use for the exception. Will be converted to a string using
+ * {@link String#valueOf(Object)}.
+ * @return the argument, if it passes the null check.
+ */
+ public static <T /*>>> extends @NonNull Object*/> T checkNotNull(
+ T arg, @javax.annotation.Nullable Object errorMessage) {
+ if (arg == null) {
+ throw new NullPointerException(String.valueOf(errorMessage));
+ }
+ return arg;
+ }
+
+ /**
+ * Throws a {@link NullPointerException} if any of the list elements is null.
+ *
+ * @param list the argument list to check for null.
+ * @param errorMessage the message to use for the exception. Will be converted to a string using
+ * {@link String#valueOf(Object)}.
+ */
+ public static <T /*>>> extends @NonNull Object*/> void checkListElementNotNull(
+ List<T> list, @javax.annotation.Nullable Object errorMessage) {
+ for (T element : list) {
+ if (element == null) {
+ throw new NullPointerException(String.valueOf(errorMessage));
+ }
+ }
+ }
+
+ /**
+ * Compares two Objects for equality. This functionality is provided by {@code
+ * Objects.equal(Object, Object)} in Java 7.
+ */
+ public static boolean equalsObjects(
+ @javax.annotation.Nullable Object x, @javax.annotation.Nullable Object y) {
+ return x == null ? y == null : x.equals(y);
+ }
+
+ /**
+ * Substitutes each {@code %s} in {@code template} with an argument. These are matched by
+ * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than
+ * placeholders, the unmatched arguments will be appended to the end of the formatted message in
+ * square braces.
+ *
+ * <p>Copied from {@code Preconditions.format(String, Object...)} from Guava
+ *
+ * @param template a non-null string containing 0 or more {@code %s} placeholders.
+ * @param args the arguments to be substituted into the message template. Arguments are converted
+ * to strings using {@link String#valueOf(Object)}. Arguments can be null.
+ */
+ // Note that this is somewhat-improperly used from Verify.java as well.
+ private static String format(String template, @javax.annotation.Nullable Object... args) {
+ // If no arguments return the template.
+ if (args == null) {
+ return template;
+ }
+
+ // start substituting the arguments into the '%s' placeholders
+ StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
+ int templateStart = 0;
+ int i = 0;
+ while (i < args.length) {
+ int placeholderStart = template.indexOf("%s", templateStart);
+ if (placeholderStart == -1) {
+ break;
+ }
+ builder.append(template, templateStart, placeholderStart);
+ builder.append(args[i++]);
+ templateStart = placeholderStart + 2;
+ }
+ builder.append(template, templateStart, template.length());
+
+ // if we run out of placeholders, append the extra args in square braces
+ if (i < args.length) {
+ builder.append(" [");
+ builder.append(args[i++]);
+ while (i < args.length) {
+ builder.append(", ");
+ builder.append(args[i++]);
+ }
+ builder.append(']');
+ }
+
+ return builder.toString();
+ }
+}
diff --git a/api/src/main/java/io/opencensus/internal/ZeroTimeClock.java b/api/src/main/java/io/opencensus/internal/ZeroTimeClock.java
new file mode 100644
index 00000000..fda13e9e
--- /dev/null
+++ b/api/src/main/java/io/opencensus/internal/ZeroTimeClock.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017, 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.internal;
+
+import io.opencensus.common.Clock;
+import io.opencensus.common.Timestamp;
+import javax.annotation.concurrent.Immutable;
+
+/** A {@link Clock} that always returns 0. */
+@Immutable
+public final class ZeroTimeClock extends Clock {
+ private static final ZeroTimeClock INSTANCE = new ZeroTimeClock();
+ private static final Timestamp ZERO_TIMESTAMP = Timestamp.create(0, 0);
+
+ private ZeroTimeClock() {}
+
+ /**
+ * Returns a {@code ZeroTimeClock}.
+ *
+ * @return a {@code ZeroTimeClock}.
+ */
+ public static ZeroTimeClock getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public Timestamp now() {
+ return ZERO_TIMESTAMP;
+ }
+
+ @Override
+ public long nowNanos() {
+ return 0;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/internal/package-info.java b/api/src/main/java/io/opencensus/internal/package-info.java
new file mode 100644
index 00000000..5dd35b23
--- /dev/null
+++ b/api/src/main/java/io/opencensus/internal/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017, 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.
+ */
+
+/**
+ * Interfaces and implementations that are internal to OpenCensus.
+ *
+ * <p>All the content under this package and its subpackages are considered annotated with {@link
+ * io.opencensus.common.Internal}.
+ */
+@io.opencensus.common.Internal
+package io.opencensus.internal;
diff --git a/api/src/main/java/io/opencensus/metrics/DerivedDoubleGauge.java b/api/src/main/java/io/opencensus/metrics/DerivedDoubleGauge.java
new file mode 100644
index 00000000..3aaca153
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/DerivedDoubleGauge.java
@@ -0,0 +1,152 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.common.ToDoubleFunction;
+import io.opencensus.internal.Utils;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Derived Double Gauge metric, to report instantaneous measurement of a double value. Gauges can go
+ * both up and down. The gauges values can be negative.
+ *
+ * <p>Example: Create a Gauge with an object and a callback function.
+ *
+ * <pre>{@code
+ * class YourClass {
+ *
+ * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry();
+ *
+ * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc"));
+ * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound"));
+ *
+ * DerivedDoubleGauge gauge = metricRegistry.addDerivedDoubleGauge(
+ * "queue_size", "Pending jobs in a queue", "1", labelKeys);
+ *
+ * QueueManager queueManager = new QueueManager();
+ * gauge.createTimeSeries(labelValues, queueManager,
+ * new ToDoubleFunction<QueueManager>() {
+ * {@literal @}Override
+ * public double applyAsDouble(QueueManager queue) {
+ * return queue.size();
+ * }
+ * });
+ *
+ * void doWork() {
+ * // Your code here.
+ * }
+ * }
+ *
+ * }</pre>
+ *
+ * @since 0.17
+ */
+@ThreadSafe
+public abstract class DerivedDoubleGauge {
+ /**
+ * Creates a {@code TimeSeries}. The value of a single point in the TimeSeries is observed from a
+ * callback function. This function is invoked whenever metrics are collected, meaning the
+ * reported value is up-to-date. It keeps a {@link WeakReference} to the object and it is the
+ * user's responsibility to manage the lifetime of the object.
+ *
+ * @param labelValues the list of label values.
+ * @param obj the state object from which the function derives a measurement.
+ * @param function the function to be called.
+ * @param <T> the type of the object upon which the function derives a measurement.
+ * @throws NullPointerException if {@code labelValues} is null OR any element of {@code
+ * labelValues} is null OR {@code function} is null.
+ * @throws IllegalArgumentException if different time series with the same labels already exists
+ * OR if number of {@code labelValues}s are not equal to the label keys.
+ * @since 0.17
+ */
+ public abstract <T> void createTimeSeries(
+ List<LabelValue> labelValues,
+ /*@Nullable*/ T obj,
+ ToDoubleFunction</*@Nullable*/ T> function);
+
+ /**
+ * Removes the {@code TimeSeries} from the gauge metric, if it is present.
+ *
+ * @param labelValues the list of label values.
+ * @throws NullPointerException if {@code labelValues} is null.
+ * @since 0.17
+ */
+ public abstract void removeTimeSeries(List<LabelValue> labelValues);
+
+ /**
+ * Removes all {@code TimeSeries} from the gauge metric.
+ *
+ * @since 0.17
+ */
+ public abstract void clear();
+
+ /**
+ * Returns the no-op implementation of the {@code DerivedDoubleGauge}.
+ *
+ * @return the no-op implementation of the {@code DerivedDoubleGauge}.
+ * @since 0.17
+ */
+ static DerivedDoubleGauge newNoopDerivedDoubleGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return NoopDerivedDoubleGauge.create(name, description, unit, labelKeys);
+ }
+
+ /** No-op implementations of DerivedDoubleGauge class. */
+ private static final class NoopDerivedDoubleGauge extends DerivedDoubleGauge {
+ private final int labelKeysSize;
+
+ static NoopDerivedDoubleGauge create(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return new NoopDerivedDoubleGauge(name, description, unit, labelKeys);
+ }
+
+ /** Creates a new {@code NoopDerivedDoubleGauge}. */
+ NoopDerivedDoubleGauge(String name, String description, String unit, List<LabelKey> labelKeys) {
+ Utils.checkNotNull(name, "name");
+ Utils.checkNotNull(description, "description");
+ Utils.checkNotNull(unit, "unit");
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null.");
+ labelKeysSize = labelKeys.size();
+ }
+
+ @Override
+ public <T> void createTimeSeries(
+ List<LabelValue> labelValues,
+ /*@Nullable*/ T obj,
+ ToDoubleFunction</*@Nullable*/ T> function) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null.");
+ Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels.");
+ Utils.checkNotNull(function, "function");
+ }
+
+ @Override
+ public void removeTimeSeries(List<LabelValue> labelValues) {
+ Utils.checkNotNull(labelValues, "labelValues");
+ }
+
+ @Override
+ public void clear() {}
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/DerivedLongGauge.java b/api/src/main/java/io/opencensus/metrics/DerivedLongGauge.java
new file mode 100644
index 00000000..621873f9
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/DerivedLongGauge.java
@@ -0,0 +1,150 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.common.ToLongFunction;
+import io.opencensus.internal.Utils;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Derived Long Gauge metric, to report instantaneous measurement of an int64 value. Gauges can go
+ * both up and down. The gauges values can be negative.
+ *
+ * <p>Example: Create a Gauge with an object and a callback function.
+ *
+ * <pre>{@code
+ * class YourClass {
+ *
+ * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry();
+ *
+ * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc"));
+ * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound"));
+ *
+ * DerivedLongGauge gauge = metricRegistry.addDerivedLongGauge(
+ * "queue_size", "Pending jobs in a queue", "1", labelKeys);
+ *
+ * QueueManager queueManager = new QueueManager();
+ * gauge.createTimeSeries(labelValues, queueManager,
+ * new ToLongFunction<QueueManager>() {
+ * {@literal @}Override
+ * public long applyAsLong(QueueManager queue) {
+ * return queue.size();
+ * }
+ * });
+ *
+ * void doWork() {
+ * // Your code here.
+ * }
+ * }
+ *
+ * }</pre>
+ *
+ * @since 0.17
+ */
+@ThreadSafe
+public abstract class DerivedLongGauge {
+ /**
+ * Creates a {@code TimeSeries}. The value of a single point in the TimeSeries is observed from a
+ * callback function. This function is invoked whenever metrics are collected, meaning the
+ * reported value is up-to-date. It keeps a {@link WeakReference} to the object and it is the
+ * user's responsibility to manage the lifetime of the object.
+ *
+ * @param labelValues the list of label values.
+ * @param obj the state object from which the function derives a measurement.
+ * @param function the function to be called.
+ * @param <T> the type of the object upon which the function derives a measurement.
+ * @throws NullPointerException if {@code labelValues} is null OR any element of {@code
+ * labelValues} is null OR {@code function} is null.
+ * @throws IllegalArgumentException if different time series with the same labels already exists
+ * OR if number of {@code labelValues}s are not equal to the label keys.
+ * @since 0.17
+ */
+ public abstract <T> void createTimeSeries(
+ List<LabelValue> labelValues, /*@Nullable*/ T obj, ToLongFunction</*@Nullable*/ T> function);
+
+ /**
+ * Removes the {@code TimeSeries} from the gauge metric, if it is present.
+ *
+ * @param labelValues the list of label values.
+ * @throws NullPointerException if {@code labelValues} is null.
+ * @since 0.17
+ */
+ public abstract void removeTimeSeries(List<LabelValue> labelValues);
+
+ /**
+ * Removes all {@code TimeSeries} from the gauge metric.
+ *
+ * @since 0.17
+ */
+ public abstract void clear();
+
+ /**
+ * Returns the no-op implementation of the {@code DerivedLongGauge}.
+ *
+ * @return the no-op implementation of the {@code DerivedLongGauge}.
+ * @since 0.17
+ */
+ static DerivedLongGauge newNoopDerivedLongGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return NoopDerivedLongGauge.create(name, description, unit, labelKeys);
+ }
+
+ /** No-op implementations of DerivedLongGauge class. */
+ private static final class NoopDerivedLongGauge extends DerivedLongGauge {
+ private final int labelKeysSize;
+
+ static NoopDerivedLongGauge create(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return new NoopDerivedLongGauge(name, description, unit, labelKeys);
+ }
+
+ /** Creates a new {@code NoopDerivedLongGauge}. */
+ NoopDerivedLongGauge(String name, String description, String unit, List<LabelKey> labelKeys) {
+ Utils.checkNotNull(name, "name");
+ Utils.checkNotNull(description, "description");
+ Utils.checkNotNull(unit, "unit");
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null.");
+ labelKeysSize = labelKeys.size();
+ }
+
+ @Override
+ public <T> void createTimeSeries(
+ List<LabelValue> labelValues,
+ /*@Nullable*/ T obj,
+ ToLongFunction</*@Nullable*/ T> function) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null.");
+ Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels.");
+ Utils.checkNotNull(function, "function");
+ }
+
+ @Override
+ public void removeTimeSeries(List<LabelValue> labelValues) {
+ Utils.checkNotNull(labelValues, "labelValues");
+ }
+
+ @Override
+ public void clear() {}
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/DoubleGauge.java b/api/src/main/java/io/opencensus/metrics/DoubleGauge.java
new file mode 100644
index 00000000..32759973
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/DoubleGauge.java
@@ -0,0 +1,213 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.internal.Utils;
+import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Double Gauge metric, to report instantaneous measurement of a double value. Gauges can go both up
+ * and down. The gauges values can be negative.
+ *
+ * <p>Example 1: Create a Gauge with default labels.
+ *
+ * <pre>{@code
+ * class YourClass {
+ *
+ * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry();
+ *
+ * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc"));
+ *
+ * DoubleGauge gauge = metricRegistry.addDoubleGauge("queue_size",
+ * "Pending jobs", "1", labelKeys);
+ *
+ * // It is recommended to keep a reference of a point for manual operations.
+ * DoublePoint defaultPoint = gauge.getDefaultTimeSeries();
+ *
+ * void doWork() {
+ * // Your code here.
+ * defaultPoint.add(10);
+ * }
+ *
+ * }
+ * }</pre>
+ *
+ * <p>Example 2: You can also use labels(keys and values) to track different types of metric.
+ *
+ * <pre>{@code
+ * class YourClass {
+ *
+ * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry();
+ *
+ * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc"));
+ * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound"));
+ *
+ * DoubleGauge gauge = metricRegistry.addDoubleGauge("queue_size",
+ * "Pending jobs", "1", labelKeys);
+ *
+ * // It is recommended to keep a reference of a point for manual operations.
+ * DoublePoint inboundPoint = gauge.getOrCreateTimeSeries(labelValues);
+ *
+ * void doSomeWork() {
+ * // Your code here.
+ * inboundPoint.set(15);
+ * }
+ *
+ * }
+ * }</pre>
+ *
+ * @since 0.17
+ */
+@ThreadSafe
+public abstract class DoubleGauge {
+
+ /**
+ * Creates a {@code TimeSeries} and returns a {@code DoublePoint} if the specified {@code
+ * labelValues} is not already associated with this gauge, else returns an existing {@code
+ * DoublePoint}.
+ *
+ * <p>It is recommended to keep a reference to the DoublePoint instead of always calling this
+ * method for manual operations.
+ *
+ * @param labelValues the list of label values. The number of label values must be the same to
+ * that of the label keys passed to {@link MetricRegistry#addDoubleGauge}.
+ * @return a {@code DoublePoint} the value of single gauge.
+ * @throws NullPointerException if {@code labelValues} is null OR any element of {@code
+ * labelValues} is null.
+ * @throws IllegalArgumentException if number of {@code labelValues}s are not equal to the label
+ * keys.
+ * @since 0.17
+ */
+ public abstract DoublePoint getOrCreateTimeSeries(List<LabelValue> labelValues);
+
+ /**
+ * Returns a {@code DoublePoint} for a gauge with all labels not set, or default labels.
+ *
+ * @return a {@code DoublePoint} for a gauge with all labels not set, or default labels.
+ * @since 0.17
+ */
+ public abstract DoublePoint getDefaultTimeSeries();
+
+ /**
+ * Removes the {@code TimeSeries} from the gauge metric, if it is present. i.e. references to
+ * previous {@code DoublePoint} objects are invalid (not part of the metric).
+ *
+ * @param labelValues the list of label values.
+ * @throws NullPointerException if {@code labelValues} is null or any element of {@code
+ * labelValues} is null.
+ * @since 0.17
+ */
+ public abstract void removeTimeSeries(List<LabelValue> labelValues);
+
+ /**
+ * Removes all {@code TimeSeries} from the gauge metric. i.e. references to all previous {@code
+ * DoublePoint} objects are invalid (not part of the metric).
+ *
+ * @since 0.17
+ */
+ public abstract void clear();
+
+ /**
+ * Returns the no-op implementation of the {@code DoubleGauge}.
+ *
+ * @return the no-op implementation of the {@code DoubleGauge}.
+ * @since 0.17
+ */
+ static DoubleGauge newNoopDoubleGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return NoopDoubleGauge.create(name, description, unit, labelKeys);
+ }
+
+ /**
+ * The value of a single point in the Gauge.TimeSeries.
+ *
+ * @since 0.17
+ */
+ public abstract static class DoublePoint {
+
+ /**
+ * Adds the given value to the current value. The values can be negative.
+ *
+ * @param amt the value to add
+ * @since 0.17
+ */
+ public abstract void add(double amt);
+
+ /**
+ * Sets the given value.
+ *
+ * @param val the new value.
+ * @since 0.17
+ */
+ public abstract void set(double val);
+ }
+
+ /** No-op implementations of DoubleGauge class. */
+ private static final class NoopDoubleGauge extends DoubleGauge {
+ private final int labelKeysSize;
+
+ static NoopDoubleGauge create(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return new NoopDoubleGauge(name, description, unit, labelKeys);
+ }
+
+ /** Creates a new {@code NoopDoublePoint}. */
+ NoopDoubleGauge(String name, String description, String unit, List<LabelKey> labelKeys) {
+ Utils.checkNotNull(name, "name");
+ Utils.checkNotNull(description, "description");
+ Utils.checkNotNull(unit, "unit");
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null.");
+ labelKeysSize = labelKeys.size();
+ }
+
+ @Override
+ public NoopDoublePoint getOrCreateTimeSeries(List<LabelValue> labelValues) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null.");
+ Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels.");
+ return NoopDoublePoint.INSTANCE;
+ }
+
+ @Override
+ public NoopDoublePoint getDefaultTimeSeries() {
+ return NoopDoublePoint.INSTANCE;
+ }
+
+ @Override
+ public void removeTimeSeries(List<LabelValue> labelValues) {
+ Utils.checkNotNull(labelValues, "labelValues");
+ }
+
+ @Override
+ public void clear() {}
+
+ /** No-op implementations of DoublePoint class. */
+ private static final class NoopDoublePoint extends DoublePoint {
+ private static final NoopDoublePoint INSTANCE = new NoopDoublePoint();
+
+ private NoopDoublePoint() {}
+
+ @Override
+ public void add(double amt) {}
+
+ @Override
+ public void set(double val) {}
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/LabelKey.java b/api/src/main/java/io/opencensus/metrics/LabelKey.java
new file mode 100644
index 00000000..efc51e64
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/LabelKey.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.metrics;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * The key of a {@code Label} associated with a {@code MetricDescriptor}.
+ *
+ * @since 0.15
+ */
+@ExperimentalApi
+@Immutable
+@AutoValue
+public abstract class LabelKey {
+
+ LabelKey() {}
+
+ /**
+ * Creates a {@link LabelKey}.
+ *
+ * @param key the key of a {@code Label}.
+ * @param description a human-readable description of what this label key represents.
+ * @return a {@code LabelKey}.
+ * @since 0.17
+ */
+ public static LabelKey create(String key, String description) {
+ return new AutoValue_LabelKey(key, description);
+ }
+
+ /**
+ * Returns the key of this {@link LabelKey}.
+ *
+ * @return the key.
+ * @since 0.17
+ */
+ public abstract String getKey();
+
+ /**
+ * Returns the description of this {@link LabelKey}.
+ *
+ * @return the description.
+ * @since 0.17
+ */
+ public abstract String getDescription();
+}
diff --git a/api/src/main/java/io/opencensus/metrics/LabelValue.java b/api/src/main/java/io/opencensus/metrics/LabelValue.java
new file mode 100644
index 00000000..e5708655
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/LabelValue.java
@@ -0,0 +1,57 @@
+/*
+ * 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.metrics;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * The value of a {@code Label} associated with a {@code TimeSeries}.
+ *
+ * @since 0.15
+ */
+@ExperimentalApi
+@Immutable
+@AutoValue
+public abstract class LabelValue {
+
+ LabelValue() {}
+
+ /**
+ * Creates a {@link LabelValue}.
+ *
+ * @param value the value of a {@code Label}. {@code null} value indicates an unset {@code
+ * LabelValue}.
+ * @return a {@code LabelValue}.
+ * @since 0.17
+ */
+ public static LabelValue create(@Nullable String value) {
+ return new AutoValue_LabelValue(value);
+ }
+
+ /**
+ * Returns the value of this {@link LabelValue}. Returns {@code null} if the value is unset and
+ * supposed to be ignored.
+ *
+ * @return the value.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract String getValue();
+}
diff --git a/api/src/main/java/io/opencensus/metrics/LongGauge.java b/api/src/main/java/io/opencensus/metrics/LongGauge.java
new file mode 100644
index 00000000..1d4489c9
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/LongGauge.java
@@ -0,0 +1,205 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.internal.Utils;
+import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Long Gauge metric, to report instantaneous measurement of an int64 value. Gauges can go both up
+ * and down. The gauges values can be negative.
+ *
+ * <p>Example 1: Create a Gauge with default labels.
+ *
+ * <pre>{@code
+ * class YourClass {
+ *
+ * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry();
+ *
+ * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc"));
+ *
+ * LongGauge gauge = metricRegistry.addLongGauge("queue_size", "Pending jobs", "1", labelKeys);
+ *
+ * // It is recommended to keep a reference of a point for manual operations.
+ * LongPoint defaultPoint = gauge.getDefaultTimeSeries();
+ *
+ * void doWork() {
+ * // Your code here.
+ * defaultPoint.add(10);
+ * }
+ *
+ * }
+ * }</pre>
+ *
+ * <p>Example 2: You can also use labels(keys and values) to track different types of metric.
+ *
+ * <pre>{@code
+ * class YourClass {
+ *
+ * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry();
+ *
+ * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc"));
+ * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound"));
+ *
+ * LongGauge gauge = metricRegistry.addLongGauge("queue_size", "Pending jobs", "1", labelKeys);
+ *
+ * // It is recommended to keep a reference of a point for manual operations.
+ * LongPoint inboundPoint = gauge.getOrCreateTimeSeries(labelValues);
+ *
+ * void doSomeWork() {
+ * // Your code here.
+ * inboundPoint.set(15);
+ * }
+ *
+ * }
+ * }</pre>
+ *
+ * @since 0.17
+ */
+@ThreadSafe
+public abstract class LongGauge {
+
+ /**
+ * Creates a {@code TimeSeries} and returns a {@code LongPoint} if the specified {@code
+ * labelValues} is not already associated with this gauge, else returns an existing {@code
+ * LongPoint}.
+ *
+ * <p>It is recommended to keep a reference to the LongPoint instead of always calling this method
+ * for manual operations.
+ *
+ * @param labelValues the list of label values. The number of label values must be the same to
+ * that of the label keys passed to {@link MetricRegistry#addLongGauge}.
+ * @return a {@code LongPoint} the value of single gauge.
+ * @throws NullPointerException if {@code labelValues} is null OR any element of {@code
+ * labelValues} is null.
+ * @throws IllegalArgumentException if number of {@code labelValues}s are not equal to the label
+ * keys passed to {@link MetricRegistry#addLongGauge}.
+ * @since 0.17
+ */
+ public abstract LongPoint getOrCreateTimeSeries(List<LabelValue> labelValues);
+
+ /**
+ * Returns a {@code LongPoint} for a gauge with all labels not set, or default labels.
+ *
+ * @return a {@code LongPoint} for a gauge with all labels not set, or default labels.
+ * @since 0.17
+ */
+ public abstract LongPoint getDefaultTimeSeries();
+
+ /**
+ * Removes the {@code TimeSeries} from the gauge metric, if it is present. i.e. references to
+ * previous {@code LongPoint} objects are invalid (not part of the metric).
+ *
+ * @param labelValues the list of label values.
+ * @throws NullPointerException if {@code labelValues} is null.
+ * @since 0.17
+ */
+ public abstract void removeTimeSeries(List<LabelValue> labelValues);
+
+ /**
+ * Removes all {@code TimeSeries} from the gauge metric. i.e. references to all previous {@code
+ * LongPoint} objects are invalid (not part of the metric).
+ *
+ * @since 0.17
+ */
+ public abstract void clear();
+
+ /**
+ * Returns the no-op implementation of the {@code LongGauge}.
+ *
+ * @return the no-op implementation of the {@code LongGauge}.
+ * @since 0.17
+ */
+ static LongGauge newNoopLongGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return NoopLongGauge.create(name, description, unit, labelKeys);
+ }
+
+ /**
+ * The value of a single point in the Gauge.TimeSeries.
+ *
+ * @since 0.17
+ */
+ public abstract static class LongPoint {
+
+ /**
+ * Adds the given value to the current value. The values can be negative.
+ *
+ * @param amt the value to add
+ * @since 0.17
+ */
+ public abstract void add(long amt);
+
+ /**
+ * Sets the given value.
+ *
+ * @param val the new value.
+ * @since 0.17
+ */
+ public abstract void set(long val);
+ }
+
+ /** No-op implementations of LongGauge class. */
+ private static final class NoopLongGauge extends LongGauge {
+ private final int labelKeysSize;
+
+ static NoopLongGauge create(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ return new NoopLongGauge(name, description, unit, labelKeys);
+ }
+
+ /** Creates a new {@code NoopLongPoint}. */
+ NoopLongGauge(String name, String description, String unit, List<LabelKey> labelKeys) {
+ labelKeysSize = labelKeys.size();
+ }
+
+ @Override
+ public NoopLongPoint getOrCreateTimeSeries(List<LabelValue> labelValues) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null.");
+ Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels.");
+ return NoopLongPoint.INSTANCE;
+ }
+
+ @Override
+ public NoopLongPoint getDefaultTimeSeries() {
+ return NoopLongPoint.INSTANCE;
+ }
+
+ @Override
+ public void removeTimeSeries(List<LabelValue> labelValues) {
+ Utils.checkNotNull(labelValues, "labelValues");
+ }
+
+ @Override
+ public void clear() {}
+
+ /** No-op implementations of LongPoint class. */
+ private static final class NoopLongPoint extends LongPoint {
+ private static final NoopLongPoint INSTANCE = new NoopLongPoint();
+
+ private NoopLongPoint() {}
+
+ @Override
+ public void add(long amt) {}
+
+ @Override
+ public void set(long val) {}
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/MetricRegistry.java b/api/src/main/java/io/opencensus/metrics/MetricRegistry.java
new file mode 100644
index 00000000..5be15594
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/MetricRegistry.java
@@ -0,0 +1,156 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.common.ToDoubleFunction;
+import io.opencensus.common.ToLongFunction;
+import io.opencensus.internal.Utils;
+import java.util.List;
+
+/**
+ * Creates and manages your application's set of metrics. The default implementation of this creates
+ * a {@link io.opencensus.metrics.export.MetricProducer} and registers it to the global {@link
+ * io.opencensus.metrics.export.MetricProducerManager}.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+public abstract class MetricRegistry {
+ /**
+ * Builds a new long gauge to be added to the registry. This is more convenient form when you want
+ * to manually increase and decrease values as per your service requirements.
+ *
+ * @param name the name of the metric.
+ * @param description the description of the metric.
+ * @param unit the unit of the metric.
+ * @param labelKeys the list of the label keys.
+ * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys}
+ * is null OR {@code name}, {@code description}, {@code unit} is null.
+ * @throws IllegalArgumentException if different metric with the same name already registered.
+ * @since 0.17
+ */
+ @ExperimentalApi
+ public abstract LongGauge addLongGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys);
+
+ /**
+ * Builds a new double gauge to be added to the registry. This is more convenient form when you
+ * want to manually increase and decrease values as per your service requirements.
+ *
+ * @param name the name of the metric.
+ * @param description the description of the metric.
+ * @param unit the unit of the metric.
+ * @param labelKeys the list of the label keys.
+ * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys}
+ * is null OR {@code name}, {@code description}, {@code unit} is null.
+ * @throws IllegalArgumentException if different metric with the same name already registered.
+ * @since 0.17
+ */
+ @ExperimentalApi
+ public abstract DoubleGauge addDoubleGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys);
+
+ /**
+ * Builds a new derived long gauge to be added to the registry. This is more convenient form when
+ * you want to define a gauge by executing a {@link ToLongFunction} on an object.
+ *
+ * @param name the name of the metric.
+ * @param description the description of the metric.
+ * @param unit the unit of the metric.
+ * @param labelKeys the list of the label keys.
+ * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys}
+ * is null OR {@code name}, {@code description}, {@code unit} is null.
+ * @throws IllegalArgumentException if different metric with the same name already registered.
+ * @since 0.17
+ */
+ @ExperimentalApi
+ public abstract DerivedLongGauge addDerivedLongGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys);
+
+ /**
+ * Builds a new derived double gauge to be added to the registry. This is more convenient form
+ * when you want to define a gauge by executing a {@link ToDoubleFunction} on an object.
+ *
+ * @param name the name of the metric.
+ * @param description the description of the metric.
+ * @param unit the unit of the metric.
+ * @param labelKeys the list of the label keys.
+ * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys}
+ * is null OR {@code name}, {@code description}, {@code unit} is null.
+ * @throws IllegalArgumentException if different metric with the same name already registered.
+ * @since 0.17
+ */
+ @ExperimentalApi
+ public abstract DerivedDoubleGauge addDerivedDoubleGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys);
+
+ static MetricRegistry newNoopMetricRegistry() {
+ return new NoopMetricRegistry();
+ }
+
+ private static final class NoopMetricRegistry extends MetricRegistry {
+
+ @Override
+ public LongGauge addLongGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null.");
+ return LongGauge.newNoopLongGauge(
+ Utils.checkNotNull(name, "name"),
+ Utils.checkNotNull(description, "description"),
+ Utils.checkNotNull(unit, "unit"),
+ labelKeys);
+ }
+
+ @Override
+ public DoubleGauge addDoubleGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null.");
+ return DoubleGauge.newNoopDoubleGauge(
+ Utils.checkNotNull(name, "name"),
+ Utils.checkNotNull(description, "description"),
+ Utils.checkNotNull(unit, "unit"),
+ labelKeys);
+ }
+
+ @Override
+ public DerivedLongGauge addDerivedLongGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null.");
+ return DerivedLongGauge.newNoopDerivedLongGauge(
+ Utils.checkNotNull(name, "name"),
+ Utils.checkNotNull(description, "description"),
+ Utils.checkNotNull(unit, "unit"),
+ labelKeys);
+ }
+
+ @Override
+ public DerivedDoubleGauge addDerivedDoubleGauge(
+ String name, String description, String unit, List<LabelKey> labelKeys) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null.");
+ return DerivedDoubleGauge.newNoopDerivedDoubleGauge(
+ Utils.checkNotNull(name, "name"),
+ Utils.checkNotNull(description, "description"),
+ Utils.checkNotNull(unit, "unit"),
+ labelKeys);
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/Metrics.java b/api/src/main/java/io/opencensus/metrics/Metrics.java
new file mode 100644
index 00000000..920a4a88
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/Metrics.java
@@ -0,0 +1,96 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.internal.DefaultVisibilityForTesting;
+import io.opencensus.internal.Provider;
+import io.opencensus.metrics.export.ExportComponent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Class for accessing the default {@link MetricsComponent}.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+public final class Metrics {
+ private static final Logger logger = Logger.getLogger(Metrics.class.getName());
+ private static final MetricsComponent metricsComponent =
+ loadMetricsComponent(MetricsComponent.class.getClassLoader());
+
+ /**
+ * Returns the global {@link ExportComponent}.
+ *
+ * @return the global {@code ExportComponent}.
+ * @since 0.17
+ */
+ public static ExportComponent getExportComponent() {
+ return metricsComponent.getExportComponent();
+ }
+
+ /**
+ * Returns the global {@link MetricRegistry}.
+ *
+ * <p>This {@code MetricRegistry} is already added to the global {@link
+ * io.opencensus.metrics.export.MetricProducerManager}.
+ *
+ * @return the global {@code MetricRegistry}.
+ * @since 0.17
+ */
+ public static MetricRegistry getMetricRegistry() {
+ return metricsComponent.getMetricRegistry();
+ }
+
+ // Any provider that may be used for MetricsComponent can be added here.
+ @DefaultVisibilityForTesting
+ static MetricsComponent loadMetricsComponent(@Nullable ClassLoader classLoader) {
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impl.metrics.MetricsComponentImpl", /*initialize=*/ true, classLoader),
+ MetricsComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load full implementation for MetricsComponent, now trying to load lite "
+ + "implementation.",
+ e);
+ }
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impllite.metrics.MetricsComponentImplLite",
+ /*initialize=*/ true,
+ classLoader),
+ MetricsComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load lite implementation for MetricsComponent, now using default "
+ + "implementation for MetricsComponent.",
+ e);
+ }
+ return MetricsComponent.newNoopMetricsComponent();
+ }
+
+ private Metrics() {}
+}
diff --git a/api/src/main/java/io/opencensus/metrics/MetricsComponent.java b/api/src/main/java/io/opencensus/metrics/MetricsComponent.java
new file mode 100644
index 00000000..3a992306
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/MetricsComponent.java
@@ -0,0 +1,71 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.metrics.export.ExportComponent;
+
+/**
+ * Class that holds the implementation instance for {@link ExportComponent}.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+public abstract class MetricsComponent {
+
+ /**
+ * Returns the {@link ExportComponent} with the provided implementation. If no implementation is
+ * provided then no-op implementations will be used.
+ *
+ * @return the {@link ExportComponent} implementation.
+ * @since 0.17
+ */
+ public abstract ExportComponent getExportComponent();
+
+ /**
+ * Returns the {@link MetricRegistry} with the provided implementation.
+ *
+ * @return the {@link MetricRegistry} implementation.
+ * @since 0.17
+ */
+ public abstract MetricRegistry getMetricRegistry();
+
+ /**
+ * Returns an instance that contains no-op implementations for all the instances.
+ *
+ * @return an instance that contains no-op implementations for all the instances.
+ */
+ static MetricsComponent newNoopMetricsComponent() {
+ return new NoopMetricsComponent();
+ }
+
+ private static final class NoopMetricsComponent extends MetricsComponent {
+ private static final ExportComponent EXPORT_COMPONENT =
+ ExportComponent.newNoopExportComponent();
+ private static final MetricRegistry METRIC_REGISTRY = MetricRegistry.newNoopMetricRegistry();
+
+ @Override
+ public ExportComponent getExportComponent() {
+ return EXPORT_COMPONENT;
+ }
+
+ @Override
+ public MetricRegistry getMetricRegistry() {
+ return METRIC_REGISTRY;
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/Distribution.java b/api/src/main/java/io/opencensus/metrics/export/Distribution.java
new file mode 100644
index 00000000..d55f101c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/Distribution.java
@@ -0,0 +1,345 @@
+/*
+ * 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.metrics.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.common.Function;
+import io.opencensus.common.Timestamp;
+import io.opencensus.internal.Utils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * {@link Distribution} contains summary statistics for a population of values. It optionally
+ * contains a histogram representing the distribution of those values across a set of buckets.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@AutoValue
+@Immutable
+public abstract class Distribution {
+
+ Distribution() {}
+
+ /**
+ * Creates a {@link Distribution}.
+ *
+ * @param count the count of the population values.
+ * @param sum the sum of the population values.
+ * @param sumOfSquaredDeviations the sum of squared deviations of the population values.
+ * @param bucketOptions the bucket options used to create a histogram for the distribution.
+ * @param buckets {@link Bucket}s of a histogram.
+ * @return a {@code Distribution}.
+ * @since 0.17
+ */
+ public static Distribution create(
+ long count,
+ double sum,
+ double sumOfSquaredDeviations,
+ BucketOptions bucketOptions,
+ List<Bucket> buckets) {
+ Utils.checkArgument(count >= 0, "count should be non-negative.");
+ Utils.checkArgument(
+ sumOfSquaredDeviations >= 0, "sum of squared deviations should be non-negative.");
+ if (count == 0) {
+ Utils.checkArgument(sum == 0, "sum should be 0 if count is 0.");
+ Utils.checkArgument(
+ sumOfSquaredDeviations == 0, "sum of squared deviations should be 0 if count is 0.");
+ }
+ Utils.checkNotNull(bucketOptions, "bucketOptions");
+ List<Bucket> bucketsCopy =
+ Collections.unmodifiableList(new ArrayList<Bucket>(Utils.checkNotNull(buckets, "buckets")));
+ Utils.checkListElementNotNull(bucketsCopy, "bucket");
+ return new AutoValue_Distribution(
+ count, sum, sumOfSquaredDeviations, bucketOptions, bucketsCopy);
+ }
+
+ /**
+ * Returns the aggregated count.
+ *
+ * @return the aggregated count.
+ * @since 0.17
+ */
+ public abstract long getCount();
+
+ /**
+ * Returns the aggregated sum.
+ *
+ * @return the aggregated sum.
+ * @since 0.17
+ */
+ public abstract double getSum();
+
+ /**
+ * Returns the aggregated sum of squared deviations.
+ *
+ * <p>The sum of squared deviations from the mean of the values in the population. For values x_i
+ * this is:
+ *
+ * <p>Sum[i=1..n]((x_i - mean)^2)
+ *
+ * <p>If count is zero then this field must be zero.
+ *
+ * @return the aggregated sum of squared deviations.
+ * @since 0.17
+ */
+ public abstract double getSumOfSquaredDeviations();
+
+ /**
+ * Returns bucket options used to create a histogram for the distribution.
+ *
+ * @return the {@code BucketOptions} associated with the {@code Distribution}, or {@code null} if
+ * there isn't one.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract BucketOptions getBucketOptions();
+
+ /**
+ * Returns the aggregated histogram {@link Bucket}s.
+ *
+ * @return the aggregated histogram buckets.
+ * @since 0.17
+ */
+ public abstract List<Bucket> getBuckets();
+
+ /**
+ * The bucket options used to create a histogram for the distribution.
+ *
+ * @since 0.17
+ */
+ @Immutable
+ public abstract static class BucketOptions {
+
+ private BucketOptions() {}
+
+ /**
+ * Returns a {@link ExplicitOptions}.
+ *
+ * <p>The bucket boundaries for that histogram are described by bucket_bounds. This defines
+ * size(bucket_bounds) + 1 (= N) buckets. The boundaries for bucket index i are:
+ *
+ * <ul>
+ * <li>{@code [0, bucket_bounds[i]) for i == 0}
+ * <li>{@code [bucket_bounds[i-1], bucket_bounds[i]) for 0 < i < N-1}
+ * <li>{@code [bucket_bounds[i-1], +infinity) for i == N-1}
+ * </ul>
+ *
+ * <p>If bucket_bounds has no elements (zero size), then there is no histogram associated with
+ * the Distribution. If bucket_bounds has only one element, there are no finite buckets, and
+ * that single element is the common boundary of the overflow and underflow buckets. The values
+ * must be monotonically increasing.
+ *
+ * @param bucketBoundaries the bucket boundaries of a distribution (given explicitly). The
+ * values must be strictly increasing and should be positive values.
+ * @return a {@code ExplicitOptions} {@code BucketOptions}.
+ * @since 0.17
+ */
+ public static BucketOptions explicitOptions(List<Double> bucketBoundaries) {
+ return ExplicitOptions.create(bucketBoundaries);
+ }
+
+ /**
+ * Applies the given match function to the underlying BucketOptions.
+ *
+ * @param explicitFunction the function that should be applied if the BucketOptions has type
+ * {@code ExplicitOptions}.
+ * @param defaultFunction the function that should be applied if the BucketOptions has a type
+ * that was added after this {@code match} method was added to the API. See {@link
+ * io.opencensus.common.Functions} for some common functions for handling unknown types.
+ * @return the result of the function applied to the underlying BucketOptions.
+ * @since 0.17
+ */
+ public abstract <T> T match(
+ Function<? super ExplicitOptions, T> explicitFunction,
+ Function<? super BucketOptions, T> defaultFunction);
+
+ /** A Bucket with explicit bounds {@link BucketOptions}. */
+ @AutoValue
+ @Immutable
+ public abstract static class ExplicitOptions extends BucketOptions {
+
+ ExplicitOptions() {}
+
+ @Override
+ public final <T> T match(
+ Function<? super ExplicitOptions, T> explicitFunction,
+ Function<? super BucketOptions, T> defaultFunction) {
+ return explicitFunction.apply(this);
+ }
+
+ /**
+ * Creates a {@link ExplicitOptions}.
+ *
+ * @param bucketBoundaries the bucket boundaries of a distribution (given explicitly). The
+ * values must be strictly increasing and should be positive.
+ * @return a {@code ExplicitOptions}.
+ * @since 0.17
+ */
+ private static ExplicitOptions create(List<Double> bucketBoundaries) {
+ Utils.checkNotNull(bucketBoundaries, "bucketBoundaries");
+ List<Double> bucketBoundariesCopy =
+ Collections.unmodifiableList(new ArrayList<Double>(bucketBoundaries));
+ checkBucketBoundsAreSorted(bucketBoundariesCopy);
+ return new AutoValue_Distribution_BucketOptions_ExplicitOptions(bucketBoundariesCopy);
+ }
+
+ private static void checkBucketBoundsAreSorted(List<Double> bucketBoundaries) {
+ if (bucketBoundaries.size() >= 1) {
+ double previous = Utils.checkNotNull(bucketBoundaries.get(0), "bucketBoundary");
+ Utils.checkArgument(previous > 0, "bucket boundary should be > 0");
+ for (int i = 1; i < bucketBoundaries.size(); i++) {
+ double next = Utils.checkNotNull(bucketBoundaries.get(i), "bucketBoundary");
+ Utils.checkArgument(previous < next, "bucket boundaries not sorted.");
+ previous = next;
+ }
+ }
+ }
+
+ /**
+ * Returns the bucket boundaries of this distribution.
+ *
+ * @return the bucket boundaries of this distribution.
+ * @since 0.17
+ */
+ public abstract List<Double> getBucketBoundaries();
+ }
+ }
+
+ /**
+ * The histogram bucket of the population values.
+ *
+ * @since 0.17
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class Bucket {
+
+ Bucket() {}
+
+ /**
+ * Creates a {@link Bucket}.
+ *
+ * @param count the number of values in each bucket of the histogram.
+ * @return a {@code Bucket}.
+ * @since 0.17
+ */
+ public static Bucket create(long count) {
+ Utils.checkArgument(count >= 0, "bucket count should be non-negative.");
+ return new AutoValue_Distribution_Bucket(count, null);
+ }
+
+ /**
+ * Creates a {@link Bucket} with an {@link Exemplar}.
+ *
+ * @param count the number of values in each bucket of the histogram.
+ * @param exemplar the {@code Exemplar} of this {@code Bucket}.
+ * @return a {@code Bucket}.
+ * @since 0.17
+ */
+ public static Bucket create(long count, Exemplar exemplar) {
+ Utils.checkArgument(count >= 0, "bucket count should be non-negative.");
+ Utils.checkNotNull(exemplar, "exemplar");
+ return new AutoValue_Distribution_Bucket(count, exemplar);
+ }
+
+ /**
+ * Returns the number of values in each bucket of the histogram.
+ *
+ * @return the number of values in each bucket of the histogram.
+ * @since 0.17
+ */
+ public abstract long getCount();
+
+ /**
+ * Returns the {@link Exemplar} associated with the {@link Bucket}, or {@code null} if there
+ * isn't one.
+ *
+ * @return the {@code Exemplar} associated with the {@code Bucket}, or {@code null} if there
+ * isn't one.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Exemplar getExemplar();
+ }
+
+ /**
+ * An example point that may be used to annotate aggregated distribution values, associated with a
+ * histogram bucket.
+ *
+ * @since 0.17
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Exemplar {
+
+ Exemplar() {}
+
+ /**
+ * Returns value of the {@link Exemplar} point.
+ *
+ * @return value of the {@code Exemplar} point.
+ * @since 0.17
+ */
+ public abstract double getValue();
+
+ /**
+ * Returns the time that this {@link Exemplar}'s value was recorded.
+ *
+ * @return the time that this {@code Exemplar}'s value was recorded.
+ * @since 0.17
+ */
+ public abstract Timestamp getTimestamp();
+
+ /**
+ * Returns the contextual information about the example value, represented as a string map.
+ *
+ * @return the contextual information about the example value.
+ * @since 0.17
+ */
+ public abstract Map<String, String> getAttachments();
+
+ /**
+ * Creates an {@link Exemplar}.
+ *
+ * @param value value of the {@link Exemplar} point.
+ * @param timestamp the time that this {@code Exemplar}'s value was recorded.
+ * @param attachments the contextual information about the example value.
+ * @return an {@code Exemplar}.
+ * @since 0.17
+ */
+ public static Exemplar create(
+ double value, Timestamp timestamp, Map<String, String> attachments) {
+ Utils.checkNotNull(attachments, "attachments");
+ Map<String, String> attachmentsCopy =
+ Collections.unmodifiableMap(new HashMap<String, String>(attachments));
+ for (Entry<String, String> entry : attachmentsCopy.entrySet()) {
+ Utils.checkNotNull(entry.getKey(), "key of attachments");
+ Utils.checkNotNull(entry.getValue(), "value of attachments");
+ }
+ return new AutoValue_Distribution_Exemplar(value, timestamp, attachmentsCopy);
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/ExportComponent.java b/api/src/main/java/io/opencensus/metrics/export/ExportComponent.java
new file mode 100644
index 00000000..11e1fdbd
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/ExportComponent.java
@@ -0,0 +1,60 @@
+/*
+ * 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.metrics.export;
+
+import io.opencensus.common.ExperimentalApi;
+
+/**
+ * Class that holds the implementation instance for {@link MetricProducerManager}.
+ *
+ * <p>Unless otherwise noted all methods (on component) results are cacheable.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+public abstract class ExportComponent {
+ /**
+ * Returns the no-op implementation of the {@code ExportComponent}.
+ *
+ * @return the no-op implementation of the {@code ExportComponent}.
+ * @since 0.17
+ */
+ public static ExportComponent newNoopExportComponent() {
+ return new NoopExportComponent();
+ }
+
+ /**
+ * Returns the global {@link MetricProducerManager} which can be used to register handlers to
+ * export all the recorded metrics.
+ *
+ * @return the implementation of the {@code MetricExporter} or no-op if no implementation linked
+ * in the binary.
+ * @since 0.17
+ */
+ public abstract MetricProducerManager getMetricProducerManager();
+
+ private static final class NoopExportComponent extends ExportComponent {
+
+ private static final MetricProducerManager METRIC_PRODUCER_MANAGER =
+ MetricProducerManager.newNoopMetricProducerManager();
+
+ @Override
+ public MetricProducerManager getMetricProducerManager() {
+ return METRIC_PRODUCER_MANAGER;
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/Metric.java b/api/src/main/java/io/opencensus/metrics/export/Metric.java
new file mode 100644
index 00000000..7b93fc86
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/Metric.java
@@ -0,0 +1,137 @@
+/*
+ * 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.metrics.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.internal.Utils;
+import io.opencensus.metrics.export.Value.ValueDistribution;
+import io.opencensus.metrics.export.Value.ValueDouble;
+import io.opencensus.metrics.export.Value.ValueLong;
+import io.opencensus.metrics.export.Value.ValueSummary;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A {@link Metric} with one or more {@link TimeSeries}.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@Immutable
+@AutoValue
+public abstract class Metric {
+
+ Metric() {}
+
+ /**
+ * Creates a {@link Metric}.
+ *
+ * @param metricDescriptor the {@link MetricDescriptor}.
+ * @param timeSeriesList the {@link TimeSeries} list for this metric.
+ * @return a {@code Metric}.
+ * @since 0.17
+ */
+ public static Metric create(MetricDescriptor metricDescriptor, List<TimeSeries> timeSeriesList) {
+ Utils.checkListElementNotNull(
+ Utils.checkNotNull(timeSeriesList, "timeSeriesList"), "timeSeries");
+ return createInternal(
+ metricDescriptor, Collections.unmodifiableList(new ArrayList<TimeSeries>(timeSeriesList)));
+ }
+
+ /**
+ * Creates a {@link Metric}.
+ *
+ * @param metricDescriptor the {@link MetricDescriptor}.
+ * @param timeSeries the single {@link TimeSeries} for this metric.
+ * @return a {@code Metric}.
+ * @since 0.17
+ */
+ public static Metric createWithOneTimeSeries(
+ MetricDescriptor metricDescriptor, TimeSeries timeSeries) {
+ return createInternal(
+ metricDescriptor, Collections.singletonList(Utils.checkNotNull(timeSeries, "timeSeries")));
+ }
+
+ /**
+ * Creates a {@link Metric}.
+ *
+ * @param metricDescriptor the {@link MetricDescriptor}.
+ * @param timeSeriesList the {@link TimeSeries} list for this metric.
+ * @return a {@code Metric}.
+ * @since 0.17
+ */
+ private static Metric createInternal(
+ MetricDescriptor metricDescriptor, List<TimeSeries> timeSeriesList) {
+ Utils.checkNotNull(metricDescriptor, "metricDescriptor");
+ checkTypeMatch(metricDescriptor.getType(), timeSeriesList);
+ return new AutoValue_Metric(metricDescriptor, timeSeriesList);
+ }
+
+ /**
+ * Returns the {@link MetricDescriptor} of this metric.
+ *
+ * @return the {@code MetricDescriptor} of this metric.
+ * @since 0.17
+ */
+ public abstract MetricDescriptor getMetricDescriptor();
+
+ /**
+ * Returns the {@link TimeSeries} list for this metric.
+ *
+ * <p>The type of the {@link TimeSeries#getPoints()} must match {@link MetricDescriptor.Type}.
+ *
+ * @return the {@code TimeSeriesList} for this metric.
+ * @since 0.17
+ */
+ public abstract List<TimeSeries> getTimeSeriesList();
+
+ private static void checkTypeMatch(MetricDescriptor.Type type, List<TimeSeries> timeSeriesList) {
+ for (TimeSeries timeSeries : timeSeriesList) {
+ for (Point point : timeSeries.getPoints()) {
+ Value value = point.getValue();
+ String valueClassName = "";
+ if (value.getClass().getSuperclass() != null) { // work around nullness check
+ // AutoValue classes should always have a super class.
+ valueClassName = value.getClass().getSuperclass().getSimpleName();
+ }
+ switch (type) {
+ case GAUGE_INT64:
+ case CUMULATIVE_INT64:
+ Utils.checkArgument(
+ value instanceof ValueLong, "Type mismatch: %s, %s.", type, valueClassName);
+ break;
+ case CUMULATIVE_DOUBLE:
+ case GAUGE_DOUBLE:
+ Utils.checkArgument(
+ value instanceof ValueDouble, "Type mismatch: %s, %s.", type, valueClassName);
+ break;
+ case GAUGE_DISTRIBUTION:
+ case CUMULATIVE_DISTRIBUTION:
+ Utils.checkArgument(
+ value instanceof ValueDistribution, "Type mismatch: %s, %s.", type, valueClassName);
+ break;
+ case SUMMARY:
+ Utils.checkArgument(
+ value instanceof ValueSummary, "Type mismatch: %s, %s.", type, valueClassName);
+ }
+ }
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/MetricDescriptor.java b/api/src/main/java/io/opencensus/metrics/export/MetricDescriptor.java
new file mode 100644
index 00000000..a4629f8e
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/MetricDescriptor.java
@@ -0,0 +1,173 @@
+/*
+ * 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.metrics.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.internal.Utils;
+import io.opencensus.metrics.LabelKey;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * {@link MetricDescriptor} defines a {@code Metric} type and its schema.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@Immutable
+@AutoValue
+public abstract class MetricDescriptor {
+
+ MetricDescriptor() {}
+
+ /**
+ * Creates a {@link MetricDescriptor}.
+ *
+ * @param name name of {@code MetricDescriptor}.
+ * @param description description of {@code MetricDescriptor}.
+ * @param unit the metric unit.
+ * @param type type of {@code MetricDescriptor}.
+ * @param labelKeys the label keys associated with the {@code MetricDescriptor}.
+ * @return a {@code MetricDescriptor}.
+ * @since 0.17
+ */
+ public static MetricDescriptor create(
+ String name, String description, String unit, Type type, List<LabelKey> labelKeys) {
+ Utils.checkNotNull(labelKeys, "labelKeys");
+ Utils.checkListElementNotNull(labelKeys, "labelKey");
+ return new AutoValue_MetricDescriptor(
+ name,
+ description,
+ unit,
+ type,
+ Collections.unmodifiableList(new ArrayList<LabelKey>(labelKeys)));
+ }
+
+ /**
+ * Returns the metric descriptor name.
+ *
+ * @return the metric descriptor name.
+ * @since 0.17
+ */
+ public abstract String getName();
+
+ /**
+ * Returns the description of this metric descriptor.
+ *
+ * @return the description of this metric descriptor.
+ * @since 0.17
+ */
+ public abstract String getDescription();
+
+ /**
+ * Returns the unit of this metric descriptor.
+ *
+ * @return the unit of this metric descriptor.
+ * @since 0.17
+ */
+ public abstract String getUnit();
+
+ /**
+ * Returns the type of this metric descriptor.
+ *
+ * @return the type of this metric descriptor.
+ * @since 0.17
+ */
+ public abstract Type getType();
+
+ /**
+ * Returns the label keys associated with this metric descriptor.
+ *
+ * @return the label keys associated with this metric descriptor.
+ * @since 0.17
+ */
+ public abstract List<LabelKey> getLabelKeys();
+
+ /**
+ * The kind of metric. It describes how the data is reported.
+ *
+ * <p>A gauge is an instantaneous measurement of a value.
+ *
+ * <p>A cumulative measurement is a value accumulated over a time interval. In a time series,
+ * cumulative measurements should have the same start time and increasing end times, until an
+ * event resets the cumulative value to zero and sets a new start time for the following points.
+ *
+ * @since 0.17
+ */
+ public enum Type {
+
+ /**
+ * An instantaneous measurement of an int64 value.
+ *
+ * @since 0.17
+ */
+ GAUGE_INT64,
+
+ /**
+ * An instantaneous measurement of a double value.
+ *
+ * @since 0.17
+ */
+ GAUGE_DOUBLE,
+
+ /**
+ * An instantaneous measurement of a distribution value. The count and sum can go both up and
+ * down. Used in scenarios like a snapshot of time the current items in a queue have spent
+ * there.
+ *
+ * @since 0.17
+ */
+ GAUGE_DISTRIBUTION,
+
+ /**
+ * An cumulative measurement of an int64 value.
+ *
+ * @since 0.17
+ */
+ CUMULATIVE_INT64,
+
+ /**
+ * An cumulative measurement of a double value.
+ *
+ * @since 0.17
+ */
+ CUMULATIVE_DOUBLE,
+
+ /**
+ * An cumulative measurement of a distribution value. The count and sum can only go up, if
+ * resets then the start_time should also be reset.
+ *
+ * @since 0.17
+ */
+ CUMULATIVE_DISTRIBUTION,
+
+ /**
+ * Some frameworks implemented DISTRIBUTION as a summary of observations (usually things like
+ * request durations and response sizes). While it also provides a total count of observations
+ * and a sum of all observed values, it calculates configurable quantiles over a sliding time
+ * window.
+ *
+ * <p>This is not recommended, since it cannot be aggregated.
+ *
+ * @since 0.17
+ */
+ SUMMARY,
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/MetricProducer.java b/api/src/main/java/io/opencensus/metrics/export/MetricProducer.java
new file mode 100644
index 00000000..739a0a9f
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/MetricProducer.java
@@ -0,0 +1,40 @@
+/*
+ * 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.metrics.export;
+
+import io.opencensus.common.ExperimentalApi;
+import java.util.Collection;
+
+/**
+ * A {@link Metric} producer that can be registered for exporting using {@link
+ * MetricProducerManager}.
+ *
+ * <p>All implementation MUST be thread-safe.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+public abstract class MetricProducer {
+
+ /**
+ * Returns a collection of produced {@link Metric}s to be exported.
+ *
+ * @return a collection of produced {@link Metric}s to be exported.
+ * @since 0.17
+ */
+ public abstract Collection<Metric> getMetrics();
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/MetricProducerManager.java b/api/src/main/java/io/opencensus/metrics/export/MetricProducerManager.java
new file mode 100644
index 00000000..304d9294
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/MetricProducerManager.java
@@ -0,0 +1,88 @@
+/*
+ * 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.metrics.export;
+
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.internal.Utils;
+import java.util.Collections;
+import java.util.Set;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Keeps a set of {@link MetricProducer} that is used by exporters to determine the metrics that
+ * need to be exported.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@ThreadSafe
+public abstract class MetricProducerManager {
+
+ /**
+ * Adds the {@link MetricProducer} to the manager if it is not already present.
+ *
+ * @param metricProducer the {@code MetricProducer} to be added to the manager.
+ * @since 0.17
+ */
+ public abstract void add(MetricProducer metricProducer);
+
+ /**
+ * Removes the {@link MetricProducer} to the manager if it is present.
+ *
+ * @param metricProducer the {@code MetricProducer} to be removed from the manager.
+ * @since 0.17
+ */
+ public abstract void remove(MetricProducer metricProducer);
+
+ /**
+ * Returns all registered {@link MetricProducer}s that should be exported.
+ *
+ * <p>This method should be used by any metrics exporter that automatically exports data for
+ * {@code MetricProducer} registered with the {@code MetricProducerManager}.
+ *
+ * @return all registered {@code MetricProducer}s that should be exported.
+ * @since 0.17
+ */
+ public abstract Set<MetricProducer> getAllMetricProducer();
+
+ /**
+ * Returns a no-op implementation for {@link MetricProducerManager}.
+ *
+ * @return a no-op implementation for {@code MetricProducerManager}.
+ */
+ static MetricProducerManager newNoopMetricProducerManager() {
+ return new NoopMetricProducerManager();
+ }
+
+ private static final class NoopMetricProducerManager extends MetricProducerManager {
+
+ @Override
+ public void add(MetricProducer metricProducer) {
+ Utils.checkNotNull(metricProducer, "metricProducer");
+ }
+
+ @Override
+ public void remove(MetricProducer metricProducer) {
+ Utils.checkNotNull(metricProducer, "metricProducer");
+ }
+
+ @Override
+ public Set<MetricProducer> getAllMetricProducer() {
+ return Collections.emptySet();
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/Point.java b/api/src/main/java/io/opencensus/metrics/export/Point.java
new file mode 100644
index 00000000..1f382f9b
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/Point.java
@@ -0,0 +1,63 @@
+/*
+ * 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.metrics.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.common.Timestamp;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A timestamped measurement of a {@code TimeSeries}.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@AutoValue
+@Immutable
+public abstract class Point {
+
+ Point() {}
+
+ /**
+ * Creates a {@link Point}.
+ *
+ * @param value the {@link Value} of this {@link Point}.
+ * @param timestamp the {@link Timestamp} when this {@link Point} was recorded.
+ * @return a {@code Point}.
+ * @since 0.17
+ */
+ public static Point create(Value value, Timestamp timestamp) {
+ return new AutoValue_Point(value, timestamp);
+ }
+
+ /**
+ * Returns the {@link Value}.
+ *
+ * @return the {@code Value}.
+ * @since 0.17
+ */
+ public abstract Value getValue();
+
+ /**
+ * Returns the {@link Timestamp} when this {@link Point} was recorded.
+ *
+ * @return the {@code Timestamp}.
+ * @since 0.17
+ */
+ public abstract Timestamp getTimestamp();
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/Summary.java b/api/src/main/java/io/opencensus/metrics/export/Summary.java
new file mode 100644
index 00000000..c82ca961
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/Summary.java
@@ -0,0 +1,187 @@
+/*
+ * 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.metrics.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.internal.Utils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Implementation of the {@link Distribution} as a summary of observations.
+ *
+ * <p>This is not recommended, since it cannot be aggregated.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@AutoValue
+@Immutable
+public abstract class Summary {
+ Summary() {}
+
+ /**
+ * Creates a {@link Summary}.
+ *
+ * @param count the count of the population values.
+ * @param sum the sum of the population values.
+ * @param snapshot bucket boundaries of a histogram.
+ * @return a {@code Summary} with the given values.
+ * @since 0.17
+ */
+ public static Summary create(@Nullable Long count, @Nullable Double sum, Snapshot snapshot) {
+ checkCountAndSum(count, sum);
+ Utils.checkNotNull(snapshot, "snapshot");
+ return new AutoValue_Summary(count, sum, snapshot);
+ }
+
+ /**
+ * Returns the aggregated count. If not available returns {@code null}.
+ *
+ * @return the aggregated count.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Long getCount();
+
+ /**
+ * Returns the aggregated sum. If not available returns {@code null}.
+ *
+ * @return the aggregated sum.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Double getSum();
+
+ /**
+ * Returns the {@link Snapshot}.
+ *
+ * @return the {@code Snapshot}.
+ * @since 0.17
+ */
+ public abstract Snapshot getSnapshot();
+
+ /**
+ * Represents the summary observation of the recorded events over a sliding time window.
+ *
+ * @since 0.17
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Snapshot {
+ /**
+ * Returns the number of values in this {@code Snapshot}. If not available returns {@code null}.
+ *
+ * @return the number of values in this {@code Snapshot}.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Long getCount();
+
+ /**
+ * Returns the sum of values in this {@code Snapshot}. If not available returns {@code null}.
+ *
+ * @return the sum of values in this {@code Snapshot}.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Double getSum();
+
+ /**
+ * Returns the list of {@code ValueAtPercentile}s in this {@code Snapshot}.
+ *
+ * @return the list of {@code ValueAtPercentile}s in this {@code Snapshot}.
+ * @since 0.17
+ */
+ public abstract List<ValueAtPercentile> getValueAtPercentiles();
+
+ /**
+ * Creates a {@link Snapshot}.
+ *
+ * @param count the number of values in this {@code Snapshot}.
+ * @param sum the number of values in this {@code Snapshot}.
+ * @param valueAtPercentiles the list of {@code ValueAtPercentile}.
+ * @return a {@code Snapshot} with the given values.
+ * @since 0.17
+ */
+ public static Snapshot create(
+ @Nullable Long count, @Nullable Double sum, List<ValueAtPercentile> valueAtPercentiles) {
+ checkCountAndSum(count, sum);
+ Utils.checkNotNull(valueAtPercentiles, "valueAtPercentiles");
+ Utils.checkListElementNotNull(valueAtPercentiles, "value in valueAtPercentiles");
+ return new AutoValue_Summary_Snapshot(
+ count,
+ sum,
+ Collections.unmodifiableList(new ArrayList<ValueAtPercentile>(valueAtPercentiles)));
+ }
+
+ /**
+ * Represents the value at a given percentile of a distribution.
+ *
+ * @since 0.17
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class ValueAtPercentile {
+ /**
+ * Returns the percentile in this {@code ValueAtPercentile}.
+ *
+ * <p>Must be in the interval (0.0, 100.0].
+ *
+ * @return the percentile in this {@code ValueAtPercentile}.
+ * @since 0.17
+ */
+ public abstract double getPercentile();
+
+ /**
+ * Returns the value in this {@code ValueAtPercentile}.
+ *
+ * @return the value in this {@code ValueAtPercentile}.
+ * @since 0.17
+ */
+ public abstract double getValue();
+
+ /**
+ * Creates a {@link ValueAtPercentile}.
+ *
+ * @param percentile the percentile in this {@code ValueAtPercentile}.
+ * @param value the value in this {@code ValueAtPercentile}.
+ * @return a {@code ValueAtPercentile} with the given values.
+ * @since 0.17
+ */
+ public static ValueAtPercentile create(double percentile, double value) {
+ Utils.checkArgument(
+ 0 < percentile && percentile <= 100.0,
+ "percentile must be in the interval (0.0, 100.0]");
+ Utils.checkArgument(value >= 0, "value must be non-negative");
+ return new AutoValue_Summary_Snapshot_ValueAtPercentile(percentile, value);
+ }
+ }
+ }
+
+ private static void checkCountAndSum(@Nullable Long count, @Nullable Double sum) {
+ Utils.checkArgument(count == null || count >= 0, "count must be non-negative.");
+ Utils.checkArgument(sum == null || sum >= 0, "sum must be non-negative.");
+ if (count != null && count == 0) {
+ Utils.checkArgument(sum == null || sum == 0, "sum must be 0 if count is 0.");
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/TimeSeries.java b/api/src/main/java/io/opencensus/metrics/export/TimeSeries.java
new file mode 100644
index 00000000..bfaeae98
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/TimeSeries.java
@@ -0,0 +1,127 @@
+/*
+ * 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.metrics.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.common.Timestamp;
+import io.opencensus.internal.Utils;
+import io.opencensus.metrics.LabelKey;
+import io.opencensus.metrics.LabelValue;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A collection of data points that describes the time-varying values of a {@code Metric}.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@Immutable
+@AutoValue
+public abstract class TimeSeries {
+
+ TimeSeries() {}
+
+ /**
+ * Creates a {@link TimeSeries}.
+ *
+ * @param labelValues the {@code LabelValue}s that uniquely identify this {@code TimeSeries}.
+ * @param points the data {@code Point}s of this {@code TimeSeries}.
+ * @param startTimestamp the start {@code Timestamp} of this {@code TimeSeries}. Must be non-null
+ * for cumulative {@code Point}s.
+ * @return a {@code TimeSeries}.
+ * @since 0.17
+ */
+ public static TimeSeries create(
+ List<LabelValue> labelValues, List<Point> points, @Nullable Timestamp startTimestamp) {
+ Utils.checkNotNull(points, "points");
+ Utils.checkListElementNotNull(points, "point");
+ return createInternal(
+ labelValues, Collections.unmodifiableList(new ArrayList<Point>(points)), startTimestamp);
+ }
+
+ /**
+ * Creates a {@link TimeSeries}.
+ *
+ * @param labelValues the {@code LabelValue}s that uniquely identify this {@code TimeSeries}.
+ * @param point the single data {@code Point} of this {@code TimeSeries}.
+ * @param startTimestamp the start {@code Timestamp} of this {@code TimeSeries}. Must be non-null
+ * for cumulative {@code Point}s.
+ * @return a {@code TimeSeries}.
+ * @since 0.17
+ */
+ public static TimeSeries createWithOnePoint(
+ List<LabelValue> labelValues, Point point, @Nullable Timestamp startTimestamp) {
+ Utils.checkNotNull(point, "point");
+ return createInternal(labelValues, Collections.singletonList(point), startTimestamp);
+ }
+
+ /**
+ * Creates a {@link TimeSeries}.
+ *
+ * @param labelValues the {@code LabelValue}s that uniquely identify this {@code TimeSeries}.
+ * @param points the data {@code Point}s of this {@code TimeSeries}.
+ * @param startTimestamp the start {@code Timestamp} of this {@code TimeSeries}. Must be non-null
+ * for cumulative {@code Point}s.
+ * @return a {@code TimeSeries}.
+ */
+ private static TimeSeries createInternal(
+ List<LabelValue> labelValues, List<Point> points, @Nullable Timestamp startTimestamp) {
+ // Fail fast on null lists to prevent NullPointerException when copying the lists.
+ Utils.checkNotNull(labelValues, "labelValues");
+ Utils.checkListElementNotNull(labelValues, "labelValue");
+ return new AutoValue_TimeSeries(
+ Collections.unmodifiableList(new ArrayList<LabelValue>(labelValues)),
+ points,
+ startTimestamp);
+ }
+
+ /**
+ * Returns the set of {@link LabelValue}s that uniquely identify this {@link TimeSeries}.
+ *
+ * <p>Apply to all {@link Point}s.
+ *
+ * <p>The order of {@link LabelValue}s must match that of {@link LabelKey}s in the {@code
+ * MetricDescriptor}.
+ *
+ * @return the {@code LabelValue}s.
+ * @since 0.17
+ */
+ public abstract List<LabelValue> getLabelValues();
+
+ /**
+ * Returns the data {@link Point}s of this {@link TimeSeries}.
+ *
+ * @return the data {@code Point}s.
+ * @since 0.17
+ */
+ public abstract List<Point> getPoints();
+
+ /**
+ * Returns the start {@link Timestamp} of this {@link TimeSeries} if the {@link Point}s are
+ * cumulative, or {@code null} if the {@link Point}s are gauge.
+ *
+ * @return the start {@code Timestamp} or {@code null}.
+ * @since 0.17
+ */
+ @Nullable
+ public abstract Timestamp getStartTimestamp();
+}
diff --git a/api/src/main/java/io/opencensus/metrics/export/Value.java b/api/src/main/java/io/opencensus/metrics/export/Value.java
new file mode 100644
index 00000000..00a939c0
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/export/Value.java
@@ -0,0 +1,246 @@
+/*
+ * 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.metrics.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.common.Function;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * The actual point value for a {@link Point}.
+ *
+ * <p>Currently there are three types of {@link Value}:
+ *
+ * <ul>
+ * <li>{@code double}
+ * <li>{@code long}
+ * <li>{@link Distribution}
+ * </ul>
+ *
+ * <p>Each {@link Point} contains exactly one of the three {@link Value} types.
+ *
+ * @since 0.17
+ */
+@ExperimentalApi
+@Immutable
+public abstract class Value {
+
+ Value() {}
+
+ /**
+ * Returns a double {@link Value}.
+ *
+ * @param value value in double.
+ * @return a double {@code Value}.
+ * @since 0.17
+ */
+ public static Value doubleValue(double value) {
+ return ValueDouble.create(value);
+ }
+
+ /**
+ * Returns a long {@link Value}.
+ *
+ * @param value value in long.
+ * @return a long {@code Value}.
+ * @since 0.17
+ */
+ public static Value longValue(long value) {
+ return ValueLong.create(value);
+ }
+
+ /**
+ * Returns a {@link Distribution} {@link Value}.
+ *
+ * @param value value in {@link Distribution}.
+ * @return a {@code Distribution} {@code Value}.
+ * @since 0.17
+ */
+ public static Value distributionValue(Distribution value) {
+ return ValueDistribution.create(value);
+ }
+
+ /**
+ * Returns a {@link Summary} {@link Value}.
+ *
+ * @param value value in {@link Summary}.
+ * @return a {@code Summary} {@code Value}.
+ * @since 0.17
+ */
+ public static Value summaryValue(Summary value) {
+ return ValueSummary.create(value);
+ }
+
+ /**
+ * Applies the given match function to the underlying data type.
+ *
+ * @since 0.17
+ */
+ public abstract <T> T match(
+ Function<? super Double, T> doubleFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Distribution, T> distributionFunction,
+ Function<? super Summary, T> summaryFunction,
+ Function<? super Value, T> defaultFunction);
+
+ /** A 64-bit double-precision floating-point {@link Value}. */
+ @AutoValue
+ @Immutable
+ abstract static class ValueDouble extends Value {
+
+ ValueDouble() {}
+
+ @Override
+ public final <T> T match(
+ Function<? super Double, T> doubleFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Distribution, T> distributionFunction,
+ Function<? super Summary, T> summaryFunction,
+ Function<? super Value, T> defaultFunction) {
+ return doubleFunction.apply(getValue());
+ }
+
+ /**
+ * Creates a {@link ValueDouble}.
+ *
+ * @param value the value in double.
+ * @return a {@code ValueDouble}.
+ */
+ static ValueDouble create(double value) {
+ return new AutoValue_Value_ValueDouble(value);
+ }
+
+ /**
+ * Returns the double value.
+ *
+ * @return the double value.
+ */
+ abstract double getValue();
+ }
+
+ /** A 64-bit integer {@link Value}. */
+ @AutoValue
+ @Immutable
+ abstract static class ValueLong extends Value {
+
+ ValueLong() {}
+
+ @Override
+ public final <T> T match(
+ Function<? super Double, T> doubleFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Distribution, T> distributionFunction,
+ Function<? super Summary, T> summaryFunction,
+ Function<? super Value, T> defaultFunction) {
+ return longFunction.apply(getValue());
+ }
+
+ /**
+ * Creates a {@link ValueLong}.
+ *
+ * @param value the value in long.
+ * @return a {@code ValueLong}.
+ */
+ static ValueLong create(long value) {
+ return new AutoValue_Value_ValueLong(value);
+ }
+
+ /**
+ * Returns the long value.
+ *
+ * @return the long value.
+ */
+ abstract long getValue();
+ }
+
+ /**
+ * {@link ValueDistribution} contains summary statistics for a population of values. It optionally
+ * contains a histogram representing the distribution of those values across a set of buckets.
+ */
+ @AutoValue
+ @Immutable
+ abstract static class ValueDistribution extends Value {
+
+ ValueDistribution() {}
+
+ @Override
+ public final <T> T match(
+ Function<? super Double, T> doubleFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Distribution, T> distributionFunction,
+ Function<? super Summary, T> summaryFunction,
+ Function<? super Value, T> defaultFunction) {
+ return distributionFunction.apply(getValue());
+ }
+
+ /**
+ * Creates a {@link ValueDistribution}.
+ *
+ * @param value the {@link Distribution} value.
+ * @return a {@code ValueDistribution}.
+ */
+ static ValueDistribution create(Distribution value) {
+ return new AutoValue_Value_ValueDistribution(value);
+ }
+
+ /**
+ * Returns the {@link Distribution} value.
+ *
+ * @return the {@code Distribution} value.
+ */
+ abstract Distribution getValue();
+ }
+
+ /**
+ * {@link ValueSummary} contains a snapshot representing values calculated over an arbitrary time
+ * window.
+ */
+ @AutoValue
+ @Immutable
+ abstract static class ValueSummary extends Value {
+
+ ValueSummary() {}
+
+ @Override
+ public final <T> T match(
+ Function<? super Double, T> doubleFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Distribution, T> distributionFunction,
+ Function<? super Summary, T> summaryFunction,
+ Function<? super Value, T> defaultFunction) {
+ return summaryFunction.apply(getValue());
+ }
+
+ /**
+ * Creates a {@link ValueSummary}.
+ *
+ * @param value the {@link Summary} value.
+ * @return a {@code ValueSummary}.
+ */
+ static ValueSummary create(Summary value) {
+ return new AutoValue_Value_ValueSummary(value);
+ }
+
+ /**
+ * Returns the {@link Summary} value.
+ *
+ * @return the {@code Summary} value.
+ */
+ abstract Summary getValue();
+ }
+}
diff --git a/api/src/main/java/io/opencensus/metrics/package-info.java b/api/src/main/java/io/opencensus/metrics/package-info.java
new file mode 100644
index 00000000..33eadf0c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/metrics/package-info.java
@@ -0,0 +1,32 @@
+/*
+ * 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 describes the Metrics data model. Metrics are a data model for what stats exporters
+ * take as input. This data model may eventually become the wire format for metrics.
+ *
+ * <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.metrics}
+ * are likely to get backwards-incompatible updates in the future. DO NOT USE except for
+ * experimental purposes.
+ *
+ * <p>Please see
+ * https://github.com/census-instrumentation/opencensus-specs/blob/master/stats/Metrics.md and
+ * https://github.com/census-instrumentation/opencensus-proto/blob/master/opencensus/proto/stats/metrics/metrics.proto
+ * for more details.
+ */
+@io.opencensus.common.ExperimentalApi
+package io.opencensus.metrics;
diff --git a/api/src/main/java/io/opencensus/stats/Aggregation.java b/api/src/main/java/io/opencensus/stats/Aggregation.java
new file mode 100644
index 00000000..9c95e847
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Aggregation.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+import io.opencensus.internal.Utils;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * {@link Aggregation} is the process of combining a certain set of {@code MeasureValue}s for a
+ * given {@code Measure} into an {@link AggregationData}.
+ *
+ * <p>{@link Aggregation} currently supports 4 types of basic aggregation:
+ *
+ * <ul>
+ * <li>Sum
+ * <li>Count
+ * <li>Distribution
+ * <li>LastValue
+ * </ul>
+ *
+ * <p>When creating a {@link View}, one {@link Aggregation} needs to be specified as how to
+ * aggregate {@code MeasureValue}s.
+ *
+ * @since 0.8
+ */
+@Immutable
+public abstract class Aggregation {
+
+ private Aggregation() {}
+
+ /**
+ * Applies the given match function to the underlying data type.
+ *
+ * @since 0.13
+ */
+ public abstract <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Distribution, T> p2,
+ Function<? super LastValue, T> p3,
+ Function<? super Aggregation, T> defaultFunction);
+
+ /**
+ * Calculate sum on aggregated {@code MeasureValue}s.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Sum extends Aggregation {
+
+ Sum() {}
+
+ private static final Sum INSTANCE = new AutoValue_Aggregation_Sum();
+
+ /**
+ * Construct a {@code Sum}.
+ *
+ * @return a new {@code Sum}.
+ * @since 0.8
+ */
+ public static Sum create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Distribution, T> p2,
+ Function<? super LastValue, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return p0.apply(this);
+ }
+ }
+
+ /**
+ * Calculate count on aggregated {@code MeasureValue}s.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Count extends Aggregation {
+
+ Count() {}
+
+ private static final Count INSTANCE = new AutoValue_Aggregation_Count();
+
+ /**
+ * Construct a {@code Count}.
+ *
+ * @return a new {@code Count}.
+ * @since 0.8
+ */
+ public static Count create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Distribution, T> p2,
+ Function<? super LastValue, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return p1.apply(this);
+ }
+ }
+
+ /**
+ * Calculate mean on aggregated {@code MeasureValue}s.
+ *
+ * @since 0.8
+ * @deprecated since 0.13, use {@link Distribution} instead.
+ */
+ @Immutable
+ @AutoValue
+ @Deprecated
+ @AutoValue.CopyAnnotations
+ public abstract static class Mean extends Aggregation {
+
+ Mean() {}
+
+ private static final Mean INSTANCE = new AutoValue_Aggregation_Mean();
+
+ /**
+ * Construct a {@code Mean}.
+ *
+ * @return a new {@code Mean}.
+ * @since 0.8
+ */
+ public static Mean create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Distribution, T> p2,
+ Function<? super LastValue, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return defaultFunction.apply(this);
+ }
+ }
+
+ /**
+ * Calculate distribution stats on aggregated {@code MeasureValue}s. Distribution includes mean,
+ * count, histogram, min, max and sum of squared deviations.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Distribution extends Aggregation {
+
+ Distribution() {}
+
+ /**
+ * Construct a {@code Distribution}.
+ *
+ * @return a new {@code Distribution}.
+ * @since 0.8
+ */
+ public static Distribution create(BucketBoundaries bucketBoundaries) {
+ Utils.checkNotNull(bucketBoundaries, "bucketBoundaries");
+ return new AutoValue_Aggregation_Distribution(bucketBoundaries);
+ }
+
+ /**
+ * Returns the {@code Distribution}'s bucket boundaries.
+ *
+ * @return the {@code Distribution}'s bucket boundaries.
+ * @since 0.8
+ */
+ public abstract BucketBoundaries getBucketBoundaries();
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Distribution, T> p2,
+ Function<? super LastValue, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return p2.apply(this);
+ }
+ }
+
+ /**
+ * Calculate the last value of aggregated {@code MeasureValue}s.
+ *
+ * @since 0.13
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class LastValue extends Aggregation {
+
+ LastValue() {}
+
+ private static final LastValue INSTANCE = new AutoValue_Aggregation_LastValue();
+
+ /**
+ * Construct a {@code LastValue}.
+ *
+ * @return a new {@code LastValue}.
+ * @since 0.13
+ */
+ public static LastValue create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Distribution, T> p2,
+ Function<? super LastValue, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return p3.apply(this);
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/stats/AggregationData.java b/api/src/main/java/io/opencensus/stats/AggregationData.java
new file mode 100644
index 00000000..c6e12b67
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/AggregationData.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+import io.opencensus.common.Timestamp;
+import io.opencensus.internal.Utils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * {@link AggregationData} is the result of applying a given {@link Aggregation} to a set of {@code
+ * MeasureValue}s.
+ *
+ * <p>{@link AggregationData} currently supports 6 types of basic aggregation values:
+ *
+ * <ul>
+ * <li>SumDataDouble
+ * <li>SumDataLong
+ * <li>CountData
+ * <li>DistributionData
+ * <li>LastValueDataDouble
+ * <li>LastValueDataLong
+ * </ul>
+ *
+ * <p>{@link ViewData} will contain one {@link AggregationData}, corresponding to its {@link
+ * Aggregation} definition in {@link View}.
+ *
+ * @since 0.8
+ */
+@Immutable
+public abstract class AggregationData {
+
+ private AggregationData() {}
+
+ /**
+ * Applies the given match function to the underlying data type.
+ *
+ * @since 0.13
+ */
+ public abstract <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction);
+
+ /**
+ * The sum value of aggregated {@code MeasureValueDouble}s.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class SumDataDouble extends AggregationData {
+
+ SumDataDouble() {}
+
+ /**
+ * Creates a {@code SumDataDouble}.
+ *
+ * @param sum the aggregated sum.
+ * @return a {@code SumDataDouble}.
+ * @since 0.8
+ */
+ public static SumDataDouble create(double sum) {
+ return new AutoValue_AggregationData_SumDataDouble(sum);
+ }
+
+ /**
+ * Returns the aggregated sum.
+ *
+ * @return the aggregated sum.
+ * @since 0.8
+ */
+ public abstract double getSum();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p0.apply(this);
+ }
+ }
+
+ /**
+ * The sum value of aggregated {@code MeasureValueLong}s.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class SumDataLong extends AggregationData {
+
+ SumDataLong() {}
+
+ /**
+ * Creates a {@code SumDataLong}.
+ *
+ * @param sum the aggregated sum.
+ * @return a {@code SumDataLong}.
+ * @since 0.8
+ */
+ public static SumDataLong create(long sum) {
+ return new AutoValue_AggregationData_SumDataLong(sum);
+ }
+
+ /**
+ * Returns the aggregated sum.
+ *
+ * @return the aggregated sum.
+ * @since 0.8
+ */
+ public abstract long getSum();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p1.apply(this);
+ }
+ }
+
+ /**
+ * The count value of aggregated {@code MeasureValue}s.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class CountData extends AggregationData {
+
+ CountData() {}
+
+ /**
+ * Creates a {@code CountData}.
+ *
+ * @param count the aggregated count.
+ * @return a {@code CountData}.
+ * @since 0.8
+ */
+ public static CountData create(long count) {
+ return new AutoValue_AggregationData_CountData(count);
+ }
+
+ /**
+ * Returns the aggregated count.
+ *
+ * @return the aggregated count.
+ * @since 0.8
+ */
+ public abstract long getCount();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p2.apply(this);
+ }
+ }
+
+ /**
+ * The mean value of aggregated {@code MeasureValue}s.
+ *
+ * @since 0.8
+ * @deprecated since 0.13, use {@link DistributionData} instead.
+ */
+ @Immutable
+ @AutoValue
+ @Deprecated
+ @AutoValue.CopyAnnotations
+ public abstract static class MeanData extends AggregationData {
+
+ MeanData() {}
+
+ /**
+ * Creates a {@code MeanData}.
+ *
+ * @param mean the aggregated mean.
+ * @param count the aggregated count.
+ * @return a {@code MeanData}.
+ * @since 0.8
+ */
+ public static MeanData create(double mean, long count) {
+ return new AutoValue_AggregationData_MeanData(mean, count);
+ }
+
+ /**
+ * Returns the aggregated mean.
+ *
+ * @return the aggregated mean.
+ * @since 0.8
+ */
+ public abstract double getMean();
+
+ /**
+ * Returns the aggregated count.
+ *
+ * @return the aggregated count.
+ * @since 0.8
+ */
+ public abstract long getCount();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction) {
+ return defaultFunction.apply(this);
+ }
+ }
+
+ /**
+ * The distribution stats of aggregated {@code MeasureValue}s. Distribution stats include mean,
+ * count, histogram, min, max and sum of squared deviations.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class DistributionData extends AggregationData {
+
+ DistributionData() {}
+
+ /**
+ * Creates a {@code DistributionData}.
+ *
+ * @param mean mean value.
+ * @param count count value.
+ * @param min min value.
+ * @param max max value.
+ * @param sumOfSquaredDeviations sum of squared deviations.
+ * @param bucketCounts histogram bucket counts.
+ * @param exemplars the exemplars associated with histogram buckets.
+ * @return a {@code DistributionData}.
+ * @since 0.16
+ */
+ public static DistributionData create(
+ double mean,
+ long count,
+ double min,
+ double max,
+ double sumOfSquaredDeviations,
+ List<Long> bucketCounts,
+ List<Exemplar> exemplars) {
+ if (min != Double.POSITIVE_INFINITY || max != Double.NEGATIVE_INFINITY) {
+ Utils.checkArgument(min <= max, "max should be greater or equal to min.");
+ }
+
+ Utils.checkNotNull(bucketCounts, "bucketCounts");
+ List<Long> bucketCountsCopy = Collections.unmodifiableList(new ArrayList<Long>(bucketCounts));
+ for (Long bucket : bucketCountsCopy) {
+ Utils.checkNotNull(bucket, "bucket");
+ }
+
+ Utils.checkNotNull(exemplars, "exemplar list should not be null.");
+ for (Exemplar exemplar : exemplars) {
+ Utils.checkNotNull(exemplar, "exemplar");
+ }
+
+ return new AutoValue_AggregationData_DistributionData(
+ mean,
+ count,
+ min,
+ max,
+ sumOfSquaredDeviations,
+ bucketCountsCopy,
+ Collections.<Exemplar>unmodifiableList(new ArrayList<Exemplar>(exemplars)));
+ }
+
+ /**
+ * Creates a {@code DistributionData}.
+ *
+ * @param mean mean value.
+ * @param count count value.
+ * @param min min value.
+ * @param max max value.
+ * @param sumOfSquaredDeviations sum of squared deviations.
+ * @param bucketCounts histogram bucket counts.
+ * @return a {@code DistributionData}.
+ * @since 0.8
+ */
+ public static DistributionData create(
+ double mean,
+ long count,
+ double min,
+ double max,
+ double sumOfSquaredDeviations,
+ List<Long> bucketCounts) {
+ return create(
+ mean,
+ count,
+ min,
+ max,
+ sumOfSquaredDeviations,
+ bucketCounts,
+ Collections.<Exemplar>emptyList());
+ }
+
+ /**
+ * Returns the aggregated mean.
+ *
+ * @return the aggregated mean.
+ * @since 0.8
+ */
+ public abstract double getMean();
+
+ /**
+ * Returns the aggregated count.
+ *
+ * @return the aggregated count.
+ * @since 0.8
+ */
+ public abstract long getCount();
+
+ /**
+ * Returns the minimum of the population values.
+ *
+ * @return the minimum of the population values.
+ * @since 0.8
+ */
+ public abstract double getMin();
+
+ /**
+ * Returns the maximum of the population values.
+ *
+ * @return the maximum of the population values.
+ * @since 0.8
+ */
+ public abstract double getMax();
+
+ /**
+ * Returns the aggregated sum of squared deviations.
+ *
+ * @return the aggregated sum of squared deviations.
+ * @since 0.8
+ */
+ public abstract double getSumOfSquaredDeviations();
+
+ /**
+ * Returns the aggregated bucket counts. The returned list is immutable, trying to update it
+ * will throw an {@code UnsupportedOperationException}.
+ *
+ * @return the aggregated bucket counts.
+ * @since 0.8
+ */
+ public abstract List<Long> getBucketCounts();
+
+ /**
+ * Returns the {@link Exemplar}s associated with histogram buckets.
+ *
+ * @return the {@code Exemplar}s associated with histogram buckets.
+ * @since 0.16
+ */
+ public abstract List<Exemplar> getExemplars();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p3.apply(this);
+ }
+
+ /**
+ * An example point that may be used to annotate aggregated distribution values, associated with
+ * a histogram bucket.
+ *
+ * @since 0.16
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Exemplar {
+
+ Exemplar() {}
+
+ /**
+ * Returns value of the {@link Exemplar} point.
+ *
+ * @return value of the {@code Exemplar} point.
+ * @since 0.16
+ */
+ public abstract double getValue();
+
+ /**
+ * Returns the time that this {@link Exemplar}'s value was recorded.
+ *
+ * @return the time that this {@code Exemplar}'s value was recorded.
+ * @since 0.16
+ */
+ public abstract Timestamp getTimestamp();
+
+ /**
+ * Returns the contextual information about the example value, represented as a string map.
+ *
+ * @return the contextual information about the example value.
+ * @since 0.16
+ */
+ public abstract Map<String, String> getAttachments();
+
+ /**
+ * Creates an {@link Exemplar}.
+ *
+ * @param value value of the {@link Exemplar} point.
+ * @param timestamp the time that this {@code Exemplar}'s value was recorded.
+ * @param attachments the contextual information about the example value.
+ * @return an {@code Exemplar}.
+ * @since 0.16
+ */
+ public static Exemplar create(
+ double value, Timestamp timestamp, Map<String, String> attachments) {
+ Utils.checkNotNull(attachments, "attachments");
+ Map<String, String> attachmentsCopy =
+ Collections.unmodifiableMap(new HashMap<String, String>(attachments));
+ for (Entry<String, String> entry : attachmentsCopy.entrySet()) {
+ Utils.checkNotNull(entry.getKey(), "key of attachments");
+ Utils.checkNotNull(entry.getValue(), "value of attachments");
+ }
+ return new AutoValue_AggregationData_DistributionData_Exemplar(
+ value, timestamp, attachmentsCopy);
+ }
+ }
+ }
+
+ /**
+ * The last value of aggregated {@code MeasureValueDouble}s.
+ *
+ * @since 0.13
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class LastValueDataDouble extends AggregationData {
+
+ LastValueDataDouble() {}
+
+ /**
+ * Creates a {@code LastValueDataDouble}.
+ *
+ * @param lastValue the last value.
+ * @return a {@code LastValueDataDouble}.
+ * @since 0.13
+ */
+ public static LastValueDataDouble create(double lastValue) {
+ return new AutoValue_AggregationData_LastValueDataDouble(lastValue);
+ }
+
+ /**
+ * Returns the last value.
+ *
+ * @return the last value.
+ * @since 0.13
+ */
+ public abstract double getLastValue();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p4.apply(this);
+ }
+ }
+
+ /**
+ * The last value of aggregated {@code MeasureValueLong}s.
+ *
+ * @since 0.13
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class LastValueDataLong extends AggregationData {
+
+ LastValueDataLong() {}
+
+ /**
+ * Creates a {@code LastValueDataLong}.
+ *
+ * @param lastValue the last value.
+ * @return a {@code LastValueDataLong}.
+ * @since 0.13
+ */
+ public static LastValueDataLong create(long lastValue) {
+ return new AutoValue_AggregationData_LastValueDataLong(lastValue);
+ }
+
+ /**
+ * Returns the last value.
+ *
+ * @return the last value.
+ * @since 0.13
+ */
+ public abstract long getLastValue();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super DistributionData, T> p3,
+ Function<? super LastValueDataDouble, T> p4,
+ Function<? super LastValueDataLong, T> p5,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p5.apply(this);
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/stats/BucketBoundaries.java b/api/src/main/java/io/opencensus/stats/BucketBoundaries.java
new file mode 100644
index 00000000..61e21e6c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/BucketBoundaries.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.Utils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * The bucket boundaries for a histogram.
+ *
+ * @since 0.8
+ */
+@Immutable
+@AutoValue
+public abstract class BucketBoundaries {
+
+ /**
+ * Returns a {@code BucketBoundaries} with the given buckets.
+ *
+ * @param bucketBoundaries the boundaries for the buckets in the underlying histogram.
+ * @return a new {@code BucketBoundaries} with the specified boundaries.
+ * @throws NullPointerException if {@code bucketBoundaries} is null.
+ * @throws IllegalArgumentException if {@code bucketBoundaries} is not sorted.
+ * @since 0.8
+ */
+ public static final BucketBoundaries create(List<Double> bucketBoundaries) {
+ Utils.checkNotNull(bucketBoundaries, "bucketBoundaries");
+ List<Double> bucketBoundariesCopy = new ArrayList<Double>(bucketBoundaries); // Deep copy.
+ // Check if sorted.
+ if (bucketBoundariesCopy.size() > 1) {
+ double lower = bucketBoundariesCopy.get(0);
+ for (int i = 1; i < bucketBoundariesCopy.size(); i++) {
+ double next = bucketBoundariesCopy.get(i);
+ Utils.checkArgument(lower < next, "Bucket boundaries not sorted.");
+ lower = next;
+ }
+ }
+ return new AutoValue_BucketBoundaries(Collections.unmodifiableList(bucketBoundariesCopy));
+ }
+
+ /**
+ * Returns a list of histogram bucket boundaries.
+ *
+ * @return a list of histogram bucket boundaries.
+ * @since 0.8
+ */
+ public abstract List<Double> getBoundaries();
+}
diff --git a/api/src/main/java/io/opencensus/stats/Measure.java b/api/src/main/java/io/opencensus/stats/Measure.java
new file mode 100644
index 00000000..2de7fd70
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Measure.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+import io.opencensus.internal.DefaultVisibilityForTesting;
+import io.opencensus.internal.StringUtils;
+import io.opencensus.internal.Utils;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * The definition of the {@link Measurement} that is taken by OpenCensus library.
+ *
+ * @since 0.8
+ */
+@Immutable
+public abstract class Measure {
+ @DefaultVisibilityForTesting static final int NAME_MAX_LENGTH = 255;
+ private static final String ERROR_MESSAGE_INVALID_NAME =
+ "Name should be a ASCII string with a length no greater than "
+ + NAME_MAX_LENGTH
+ + " characters.";
+
+ /**
+ * Applies the given match function to the underlying data type.
+ *
+ * @since 0.8
+ */
+ public abstract <T> T match(
+ Function<? super MeasureDouble, T> p0,
+ Function<? super MeasureLong, T> p1,
+ Function<? super Measure, T> defaultFunction);
+
+ /**
+ * Name of measure, as a {@code String}. Should be a ASCII string with a length no greater than
+ * 255 characters.
+ *
+ * <p>Suggested format for name: {@code <web_host>/<path>}.
+ *
+ * @since 0.8
+ */
+ public abstract String getName();
+
+ /**
+ * Detailed description of the measure, used in documentation.
+ *
+ * @since 0.8
+ */
+ public abstract String getDescription();
+
+ /**
+ * The units in which {@link Measure} values are measured.
+ *
+ * <p>The suggested grammar for a unit is as follows:
+ *
+ * <ul>
+ * <li>Expression = Component { "." Component } {"/" Component };
+ * <li>Component = [ PREFIX ] UNIT [ Annotation ] | Annotation | "1";
+ * <li>Annotation = "{" NAME "}" ;
+ * </ul>
+ *
+ * <p>For example, string “MBy{transmitted}/ms” stands for megabytes per milliseconds, and the
+ * annotation transmitted inside {} is just a comment of the unit.
+ *
+ * @since 0.8
+ */
+ // TODO(songya): determine whether we want to check the grammar on string unit.
+ public abstract String getUnit();
+
+ // Prevents this class from being subclassed anywhere else.
+ private Measure() {}
+
+ /**
+ * {@link Measure} with {@code Double} typed values.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class MeasureDouble extends Measure {
+
+ MeasureDouble() {}
+
+ /**
+ * Constructs a new {@link MeasureDouble}.
+ *
+ * @param name name of {@code Measure}. Suggested format: {@code <web_host>/<path>}.
+ * @param description description of {@code Measure}.
+ * @param unit unit of {@code Measure}.
+ * @return a {@code MeasureDouble}.
+ * @since 0.8
+ */
+ public static MeasureDouble create(String name, String description, String unit) {
+ Utils.checkArgument(
+ StringUtils.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH,
+ ERROR_MESSAGE_INVALID_NAME);
+ return new AutoValue_Measure_MeasureDouble(name, description, unit);
+ }
+
+ @Override
+ public <T> T match(
+ Function<? super MeasureDouble, T> p0,
+ Function<? super MeasureLong, T> p1,
+ Function<? super Measure, T> defaultFunction) {
+ return p0.apply(this);
+ }
+
+ @Override
+ public abstract String getName();
+
+ @Override
+ public abstract String getDescription();
+
+ @Override
+ public abstract String getUnit();
+ }
+
+ /**
+ * {@link Measure} with {@code Long} typed values.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class MeasureLong extends Measure {
+
+ MeasureLong() {}
+
+ /**
+ * Constructs a new {@link MeasureLong}.
+ *
+ * @param name name of {@code Measure}. Suggested format: {@code <web_host>/<path>}.
+ * @param description description of {@code Measure}.
+ * @param unit unit of {@code Measure}.
+ * @return a {@code MeasureLong}.
+ * @since 0.8
+ */
+ public static MeasureLong create(String name, String description, String unit) {
+ Utils.checkArgument(
+ StringUtils.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH,
+ ERROR_MESSAGE_INVALID_NAME);
+ return new AutoValue_Measure_MeasureLong(name, description, unit);
+ }
+
+ @Override
+ public <T> T match(
+ Function<? super MeasureDouble, T> p0,
+ Function<? super MeasureLong, T> p1,
+ Function<? super Measure, T> defaultFunction) {
+ return p1.apply(this);
+ }
+
+ @Override
+ public abstract String getName();
+
+ @Override
+ public abstract String getDescription();
+
+ @Override
+ public abstract String getUnit();
+ }
+}
diff --git a/api/src/main/java/io/opencensus/stats/MeasureMap.java b/api/src/main/java/io/opencensus/stats/MeasureMap.java
new file mode 100644
index 00000000..beb84f06
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/MeasureMap.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import io.opencensus.internal.Utils;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Measure.MeasureLong;
+import io.opencensus.tags.TagContext;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * A map from {@link Measure}s to measured values to be recorded at the same time.
+ *
+ * @since 0.8
+ */
+@NotThreadSafe
+public abstract class MeasureMap {
+
+ /**
+ * Associates the {@link MeasureDouble} with the given value. Subsequent updates to the same
+ * {@link MeasureDouble} will overwrite the previous value.
+ *
+ * @param measure the {@link MeasureDouble}
+ * @param value the value to be associated with {@code measure}
+ * @return this
+ * @since 0.8
+ */
+ public abstract MeasureMap put(MeasureDouble measure, double value);
+
+ /**
+ * Associates the {@link MeasureLong} with the given value. Subsequent updates to the same {@link
+ * MeasureLong} will overwrite the previous value.
+ *
+ * @param measure the {@link MeasureLong}
+ * @param value the value to be associated with {@code measure}
+ * @return this
+ * @since 0.8
+ */
+ public abstract MeasureMap put(MeasureLong measure, long value);
+
+ /**
+ * Associate the contextual information of an {@code Exemplar} to this {@link MeasureMap}.
+ * Contextual information is represented as {@code String} key-value pairs.
+ *
+ * <p>If this method is called multiple times with the same key, only the last value will be kept.
+ *
+ * @param key the key of contextual information of an {@code Exemplar}.
+ * @param value the string representation of contextual information of an {@code Exemplar}.
+ * @return this
+ * @since 0.16
+ */
+ // TODO(songya): make this method abstract in the 0.17 release.
+ public MeasureMap putAttachment(String key, String value) {
+ // Provides a default no-op implementation to avoid breaking other existing sub-classes.
+ Utils.checkNotNull(key, "key");
+ Utils.checkNotNull(value, "value");
+ return this;
+ }
+
+ /**
+ * Records all of the measures at the same time, with the current {@link TagContext}.
+ *
+ * <p>This method records all of the stats in the {@code MeasureMap} every time it is called.
+ *
+ * @since 0.8
+ */
+ public abstract void record();
+
+ /**
+ * Records all of the measures at the same time, with an explicit {@link TagContext}.
+ *
+ * <p>This method records all of the stats in the {@code MeasureMap} every time it is called.
+ *
+ * @param tags the tags associated with the measurements.
+ * @since 0.8
+ */
+ public abstract void record(TagContext tags);
+}
diff --git a/api/src/main/java/io/opencensus/stats/Measurement.java b/api/src/main/java/io/opencensus/stats/Measurement.java
new file mode 100644
index 00000000..647a667d
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Measurement.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Measure.MeasureLong;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Immutable representation of a Measurement.
+ *
+ * @since 0.8
+ */
+@Immutable
+public abstract class Measurement {
+
+ /**
+ * Applies the given match function to the underlying data type.
+ *
+ * @since 0.8
+ */
+ public abstract <T> T match(
+ Function<? super MeasurementDouble, T> p0,
+ Function<? super MeasurementLong, T> p1,
+ Function<? super Measurement, T> defaultFunction);
+
+ /**
+ * Extracts the measured {@link Measure}.
+ *
+ * @since 0.8
+ */
+ public abstract Measure getMeasure();
+
+ // Prevents this class from being subclassed anywhere else.
+ private Measurement() {}
+
+ /**
+ * {@code Double} typed {@link Measurement}.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class MeasurementDouble extends Measurement {
+ MeasurementDouble() {}
+
+ /**
+ * Constructs a new {@link MeasurementDouble}.
+ *
+ * @since 0.8
+ */
+ public static MeasurementDouble create(MeasureDouble measure, double value) {
+ return new AutoValue_Measurement_MeasurementDouble(measure, value);
+ }
+
+ @Override
+ public abstract MeasureDouble getMeasure();
+
+ /**
+ * Returns the value for the measure.
+ *
+ * @return the value for the measure.
+ * @since 0.8
+ */
+ public abstract double getValue();
+
+ @Override
+ public <T> T match(
+ Function<? super MeasurementDouble, T> p0,
+ Function<? super MeasurementLong, T> p1,
+ Function<? super Measurement, T> defaultFunction) {
+ return p0.apply(this);
+ }
+ }
+
+ /**
+ * {@code Long} typed {@link Measurement}.
+ *
+ * @since 0.8
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class MeasurementLong extends Measurement {
+ MeasurementLong() {}
+
+ /**
+ * Constructs a new {@link MeasurementLong}.
+ *
+ * @since 0.8
+ */
+ public static MeasurementLong create(MeasureLong measure, long value) {
+ return new AutoValue_Measurement_MeasurementLong(measure, value);
+ }
+
+ @Override
+ public abstract MeasureLong getMeasure();
+
+ /**
+ * Returns the value for the measure.
+ *
+ * @return the value for the measure.
+ * @since 0.8
+ */
+ public abstract long getValue();
+
+ @Override
+ public <T> T match(
+ Function<? super MeasurementDouble, T> p0,
+ Function<? super MeasurementLong, T> p1,
+ Function<? super Measurement, T> defaultFunction) {
+ return p1.apply(this);
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/stats/NoopStats.java b/api/src/main/java/io/opencensus/stats/NoopStats.java
new file mode 100644
index 00000000..e7e94a38
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/NoopStats.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.internal.Utils;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Measure.MeasureLong;
+import io.opencensus.tags.TagContext;
+import io.opencensus.tags.TagValue;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/** No-op implementations of stats classes. */
+final class NoopStats {
+
+ private NoopStats() {}
+
+ /**
+ * Returns a {@code StatsComponent} that has a no-op implementation for {@link StatsRecorder}.
+ *
+ * @return a {@code StatsComponent} that has a no-op implementation for {@code StatsRecorder}.
+ */
+ static StatsComponent newNoopStatsComponent() {
+ return new NoopStatsComponent();
+ }
+
+ /**
+ * Returns a {@code StatsRecorder} that does not record any data.
+ *
+ * @return a {@code StatsRecorder} that does not record any data.
+ */
+ static StatsRecorder getNoopStatsRecorder() {
+ return NoopStatsRecorder.INSTANCE;
+ }
+
+ /**
+ * Returns a {@code MeasureMap} that ignores all calls to {@link MeasureMap#put}.
+ *
+ * @return a {@code MeasureMap} that ignores all calls to {@code MeasureMap#put}.
+ */
+ static MeasureMap getNoopMeasureMap() {
+ return NoopMeasureMap.INSTANCE;
+ }
+
+ /**
+ * Returns a {@code ViewManager} that maintains a map of views, but always returns empty {@link
+ * ViewData}s.
+ *
+ * @return a {@code ViewManager} that maintains a map of views, but always returns empty {@code
+ * ViewData}s.
+ */
+ static ViewManager newNoopViewManager() {
+ return new NoopViewManager();
+ }
+
+ @ThreadSafe
+ private static final class NoopStatsComponent extends StatsComponent {
+ private final ViewManager viewManager = newNoopViewManager();
+ private volatile boolean isRead;
+
+ @Override
+ public ViewManager getViewManager() {
+ return viewManager;
+ }
+
+ @Override
+ public StatsRecorder getStatsRecorder() {
+ return getNoopStatsRecorder();
+ }
+
+ @Override
+ public StatsCollectionState getState() {
+ isRead = true;
+ return StatsCollectionState.DISABLED;
+ }
+
+ @Override
+ @Deprecated
+ public void setState(StatsCollectionState state) {
+ Utils.checkNotNull(state, "state");
+ Utils.checkState(!isRead, "State was already read, cannot set state.");
+ }
+ }
+
+ @Immutable
+ private static final class NoopStatsRecorder extends StatsRecorder {
+ static final StatsRecorder INSTANCE = new NoopStatsRecorder();
+
+ @Override
+ public MeasureMap newMeasureMap() {
+ return getNoopMeasureMap();
+ }
+ }
+
+ @Immutable
+ private static final class NoopMeasureMap extends MeasureMap {
+ static final MeasureMap INSTANCE = new NoopMeasureMap();
+
+ @Override
+ public MeasureMap put(MeasureDouble measure, double value) {
+ return this;
+ }
+
+ @Override
+ public MeasureMap put(MeasureLong measure, long value) {
+ return this;
+ }
+
+ @Override
+ public void record() {}
+
+ @Override
+ public void record(TagContext tags) {
+ Utils.checkNotNull(tags, "tags");
+ }
+ }
+
+ @ThreadSafe
+ private static final class NoopViewManager extends ViewManager {
+ private static final Timestamp ZERO_TIMESTAMP = Timestamp.create(0, 0);
+
+ @GuardedBy("registeredViews")
+ private final Map<View.Name, View> registeredViews = new HashMap<View.Name, View>();
+
+ // Cached set of exported views. It must be set to null whenever a view is registered or
+ // unregistered.
+ @javax.annotation.Nullable private volatile Set<View> exportedViews;
+
+ @Override
+ public void registerView(View newView) {
+ Utils.checkNotNull(newView, "newView");
+ synchronized (registeredViews) {
+ exportedViews = null;
+ View existing = registeredViews.get(newView.getName());
+ Utils.checkArgument(
+ existing == null || newView.equals(existing),
+ "A different view with the same name already exists.");
+ if (existing == null) {
+ registeredViews.put(newView.getName(), newView);
+ }
+ }
+ }
+
+ @Override
+ @javax.annotation.Nullable
+ @SuppressWarnings("deprecation")
+ public ViewData getView(View.Name name) {
+ Utils.checkNotNull(name, "name");
+ synchronized (registeredViews) {
+ View view = registeredViews.get(name);
+ if (view == null) {
+ return null;
+ } else {
+ return ViewData.create(
+ view,
+ Collections.<List</*@Nullable*/ TagValue>, AggregationData>emptyMap(),
+ view.getWindow()
+ .match(
+ Functions.<ViewData.AggregationWindowData>returnConstant(
+ ViewData.AggregationWindowData.CumulativeData.create(
+ ZERO_TIMESTAMP, ZERO_TIMESTAMP)),
+ Functions.<ViewData.AggregationWindowData>returnConstant(
+ ViewData.AggregationWindowData.IntervalData.create(ZERO_TIMESTAMP)),
+ Functions.<ViewData.AggregationWindowData>throwAssertionError()));
+ }
+ }
+ }
+
+ @Override
+ public Set<View> getAllExportedViews() {
+ Set<View> views = exportedViews;
+ if (views == null) {
+ synchronized (registeredViews) {
+ exportedViews = views = filterExportedViews(registeredViews.values());
+ }
+ }
+ return views;
+ }
+
+ // Returns the subset of the given views that should be exported
+ @SuppressWarnings("deprecation")
+ private static Set<View> filterExportedViews(Collection<View> allViews) {
+ Set<View> views = new HashSet<View>();
+ for (View view : allViews) {
+ if (view.getWindow() instanceof View.AggregationWindow.Interval) {
+ continue;
+ }
+ views.add(view);
+ }
+ return Collections.unmodifiableSet(views);
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/stats/Stats.java b/api/src/main/java/io/opencensus/stats/Stats.java
new file mode 100644
index 00000000..8393f631
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Stats.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import io.opencensus.internal.DefaultVisibilityForTesting;
+import io.opencensus.internal.Provider;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Class for accessing the default {@link StatsComponent}.
+ *
+ * @since 0.8
+ */
+public final class Stats {
+ private static final Logger logger = Logger.getLogger(Stats.class.getName());
+
+ private static final StatsComponent statsComponent =
+ loadStatsComponent(StatsComponent.class.getClassLoader());
+
+ /**
+ * Returns the default {@link StatsRecorder}.
+ *
+ * @since 0.8
+ */
+ public static StatsRecorder getStatsRecorder() {
+ return statsComponent.getStatsRecorder();
+ }
+
+ /**
+ * Returns the default {@link ViewManager}.
+ *
+ * @since 0.8
+ */
+ public static ViewManager getViewManager() {
+ return statsComponent.getViewManager();
+ }
+
+ /**
+ * Returns the current {@code StatsCollectionState}.
+ *
+ * <p>When no implementation is available, {@code getState} always returns {@link
+ * StatsCollectionState#DISABLED}.
+ *
+ * <p>Once {@link #getState()} is called, subsequent calls to {@link
+ * #setState(StatsCollectionState)} will throw an {@code IllegalStateException}.
+ *
+ * @return the current {@code StatsCollectionState}.
+ * @since 0.8
+ */
+ public static StatsCollectionState getState() {
+ return statsComponent.getState();
+ }
+
+ /**
+ * Sets the current {@code StatsCollectionState}.
+ *
+ * <p>When no implementation is available, {@code setState} does not change the state.
+ *
+ * <p>If state is set to {@link StatsCollectionState#DISABLED}, all stats that are previously
+ * recorded will be cleared.
+ *
+ * @param state the new {@code StatsCollectionState}.
+ * @throws IllegalStateException if {@link #getState()} was previously called.
+ * @deprecated This method is deprecated because other libraries could cache the result of {@link
+ * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in
+ * initialization. This method throws {@link IllegalStateException} after {@code getState()}
+ * has been called, in order to limit changes to the result of {@code getState()}.
+ * @since 0.8
+ */
+ @Deprecated
+ public static void setState(StatsCollectionState state) {
+ statsComponent.setState(state);
+ }
+
+ // Any provider that may be used for StatsComponent can be added here.
+ @DefaultVisibilityForTesting
+ static StatsComponent loadStatsComponent(@Nullable ClassLoader classLoader) {
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impl.stats.StatsComponentImpl", /*initialize=*/ true, classLoader),
+ StatsComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load full implementation for StatsComponent, now trying to load lite "
+ + "implementation.",
+ e);
+ }
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impllite.stats.StatsComponentImplLite",
+ /*initialize=*/ true,
+ classLoader),
+ StatsComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load lite implementation for StatsComponent, now using "
+ + "default implementation for StatsComponent.",
+ e);
+ }
+ return NoopStats.newNoopStatsComponent();
+ }
+
+ private Stats() {}
+}
diff --git a/api/src/main/java/io/opencensus/stats/StatsCollectionState.java b/api/src/main/java/io/opencensus/stats/StatsCollectionState.java
new file mode 100644
index 00000000..6b2f2409
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/StatsCollectionState.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017, 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.stats;
+
+/**
+ * State of the {@link StatsComponent}.
+ *
+ * @since 0.8
+ */
+public enum StatsCollectionState {
+
+ /**
+ * State that fully enables stats collection.
+ *
+ * <p>The {@link StatsComponent} collects stats for registered views.
+ *
+ * @since 0.8
+ */
+ ENABLED,
+
+ /**
+ * State that disables stats collection.
+ *
+ * <p>The {@link StatsComponent} does not need to collect stats for registered views and may
+ * return empty {@link ViewData}s from {@link ViewManager#getView(View.Name)}.
+ *
+ * @since 0.8
+ */
+ DISABLED
+}
diff --git a/api/src/main/java/io/opencensus/stats/StatsComponent.java b/api/src/main/java/io/opencensus/stats/StatsComponent.java
new file mode 100644
index 00000000..9764fce5
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/StatsComponent.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017, 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.stats;
+
+/**
+ * Class that holds the implementations for {@link ViewManager} and {@link StatsRecorder}.
+ *
+ * <p>All objects returned by methods on {@code StatsComponent} are cacheable.
+ *
+ * @since 0.8
+ */
+public abstract class StatsComponent {
+
+ /**
+ * Returns the default {@link ViewManager}.
+ *
+ * @since 0.8
+ */
+ public abstract ViewManager getViewManager();
+
+ /**
+ * Returns the default {@link StatsRecorder}.
+ *
+ * @since 0.8
+ */
+ public abstract StatsRecorder getStatsRecorder();
+
+ /**
+ * Returns the current {@code StatsCollectionState}.
+ *
+ * <p>When no implementation is available, {@code getState} always returns {@link
+ * StatsCollectionState#DISABLED}.
+ *
+ * <p>Once {@link #getState()} is called, subsequent calls to {@link
+ * #setState(StatsCollectionState)} will throw an {@code IllegalStateException}.
+ *
+ * @return the current {@code StatsCollectionState}.
+ * @since 0.8
+ */
+ public abstract StatsCollectionState getState();
+
+ /**
+ * Sets the current {@code StatsCollectionState}.
+ *
+ * <p>When no implementation is available, {@code setState} does not change the state.
+ *
+ * <p>If state is set to {@link StatsCollectionState#DISABLED}, all stats that are previously
+ * recorded will be cleared.
+ *
+ * @param state the new {@code StatsCollectionState}.
+ * @throws IllegalStateException if {@link #getState()} was previously called.
+ * @deprecated This method is deprecated because other libraries could cache the result of {@link
+ * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in
+ * initialization. This method throws {@link IllegalStateException} after {@code getState()}
+ * has been called, in order to limit changes to the result of {@code getState()}.
+ * @since 0.8
+ */
+ @Deprecated
+ public abstract void setState(StatsCollectionState state);
+}
diff --git a/api/src/main/java/io/opencensus/stats/StatsRecorder.java b/api/src/main/java/io/opencensus/stats/StatsRecorder.java
new file mode 100644
index 00000000..87b8c8b0
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/StatsRecorder.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017, 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.stats;
+
+/**
+ * Provides methods to record stats against tags.
+ *
+ * @since 0.8
+ */
+public abstract class StatsRecorder {
+ // TODO(sebright): Should we provide convenience methods for only recording one measure?
+
+ /**
+ * Returns an object for recording multiple measurements.
+ *
+ * @return an object for recording multiple measurements.
+ * @since 0.8
+ */
+ public abstract MeasureMap newMeasureMap();
+}
diff --git a/api/src/main/java/io/opencensus/stats/View.java b/api/src/main/java/io/opencensus/stats/View.java
new file mode 100644
index 00000000..f563ff9a
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/View.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Duration;
+import io.opencensus.common.Function;
+import io.opencensus.internal.DefaultVisibilityForTesting;
+import io.opencensus.internal.StringUtils;
+import io.opencensus.internal.Utils;
+import io.opencensus.tags.TagKey;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A View specifies an aggregation and a set of tag keys. The aggregation will be broken down by the
+ * unique set of matching tag values for each measure.
+ *
+ * @since 0.8
+ */
+@Immutable
+@AutoValue
+@AutoValue.CopyAnnotations
+@SuppressWarnings("deprecation")
+public abstract class View {
+
+ @DefaultVisibilityForTesting static final int NAME_MAX_LENGTH = 255;
+
+ private static final Comparator<TagKey> TAG_KEY_COMPARATOR =
+ new Comparator<TagKey>() {
+ @Override
+ public int compare(TagKey key1, TagKey key2) {
+ return key1.getName().compareTo(key2.getName());
+ }
+ };
+
+ View() {}
+
+ /**
+ * Name of view. Must be unique.
+ *
+ * @since 0.8
+ */
+ public abstract Name getName();
+
+ /**
+ * More detailed description, for documentation purposes.
+ *
+ * @since 0.8
+ */
+ public abstract String getDescription();
+
+ /**
+ * Measure type of this view.
+ *
+ * @since 0.8
+ */
+ public abstract Measure getMeasure();
+
+ /**
+ * The {@link Aggregation} associated with this {@link View}.
+ *
+ * @since 0.8
+ */
+ public abstract Aggregation getAggregation();
+
+ /**
+ * Columns (a.k.a Tag Keys) to match with the associated {@link Measure}.
+ *
+ * <p>{@link Measure} will be recorded in a "greedy" way. That is, every view aggregates every
+ * measure. This is similar to doing a GROUPBY on view’s columns. Columns must be unique.
+ *
+ * @since 0.8
+ */
+ public abstract List<TagKey> getColumns();
+
+ /**
+ * Returns the time {@link AggregationWindow} for this {@code View}.
+ *
+ * @return the time {@link AggregationWindow}.
+ * @since 0.8
+ * @deprecated since 0.13. In the future all {@link View}s will be cumulative.
+ */
+ @Deprecated
+ public abstract AggregationWindow getWindow();
+
+ /**
+ * Constructs a new {@link View}.
+ *
+ * @param name the {@link Name} of view. Must be unique.
+ * @param description the description of view.
+ * @param measure the {@link Measure} to be aggregated by this view.
+ * @param aggregation the basic {@link Aggregation} that this view will support.
+ * @param columns the {@link TagKey}s that this view will aggregate on. Columns should not contain
+ * duplicates.
+ * @param window the {@link AggregationWindow} of view.
+ * @return a new {@link View}.
+ * @since 0.8
+ * @deprecated in favor of {@link #create(Name, String, Measure, Aggregation, List)}.
+ */
+ @Deprecated
+ public static View create(
+ Name name,
+ String description,
+ Measure measure,
+ Aggregation aggregation,
+ List<TagKey> columns,
+ AggregationWindow window) {
+ Utils.checkArgument(
+ new HashSet<TagKey>(columns).size() == columns.size(), "Columns have duplicate.");
+
+ List<TagKey> tagKeys = new ArrayList<TagKey>(columns);
+ Collections.sort(tagKeys, TAG_KEY_COMPARATOR);
+ return new AutoValue_View(
+ name, description, measure, aggregation, Collections.unmodifiableList(tagKeys), window);
+ }
+
+ /**
+ * Constructs a new {@link View}.
+ *
+ * @param name the {@link Name} of view. Must be unique.
+ * @param description the description of view.
+ * @param measure the {@link Measure} to be aggregated by this view.
+ * @param aggregation the basic {@link Aggregation} that this view will support.
+ * @param columns the {@link TagKey}s that this view will aggregate on. Columns should not contain
+ * duplicates.
+ * @return a new {@link View}.
+ * @since 0.13
+ */
+ public static View create(
+ Name name,
+ String description,
+ Measure measure,
+ Aggregation aggregation,
+ List<TagKey> columns) {
+ Utils.checkArgument(
+ new HashSet<TagKey>(columns).size() == columns.size(), "Columns have duplicate.");
+ return create(
+ name, description, measure, aggregation, columns, AggregationWindow.Cumulative.create());
+ }
+
+ /**
+ * The name of a {@code View}.
+ *
+ * @since 0.8
+ */
+ // This type should be used as the key when associating data with Views.
+ @Immutable
+ @AutoValue
+ public abstract static class Name {
+
+ Name() {}
+
+ /**
+ * Returns the name as a {@code String}.
+ *
+ * @return the name as a {@code String}.
+ * @since 0.8
+ */
+ public abstract String asString();
+
+ /**
+ * Creates a {@code View.Name} from a {@code String}. Should be a ASCII string with a length no
+ * greater than 255 characters.
+ *
+ * <p>Suggested format for name: {@code <web_host>/<path>}.
+ *
+ * @param name the name {@code String}.
+ * @return a {@code View.Name} with the given name {@code String}.
+ * @since 0.8
+ */
+ public static Name create(String name) {
+ Utils.checkArgument(
+ StringUtils.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH,
+ "Name should be a ASCII string with a length no greater than 255 characters.");
+ return new AutoValue_View_Name(name);
+ }
+ }
+
+ /**
+ * The time window for a {@code View}.
+ *
+ * @since 0.8
+ * @deprecated since 0.13. In the future all {@link View}s will be cumulative.
+ */
+ @Deprecated
+ @Immutable
+ public abstract static class AggregationWindow {
+
+ private AggregationWindow() {}
+
+ /**
+ * Applies the given match function to the underlying data type.
+ *
+ * @since 0.8
+ */
+ public abstract <T> T match(
+ Function<? super Cumulative, T> p0,
+ Function<? super Interval, T> p1,
+ Function<? super AggregationWindow, T> defaultFunction);
+
+ /**
+ * Cumulative (infinite interval) time {@code AggregationWindow}.
+ *
+ * @since 0.8
+ * @deprecated since 0.13. In the future all {@link View}s will be cumulative.
+ */
+ @Deprecated
+ @Immutable
+ @AutoValue
+ @AutoValue.CopyAnnotations
+ public abstract static class Cumulative extends AggregationWindow {
+
+ private static final Cumulative CUMULATIVE =
+ new AutoValue_View_AggregationWindow_Cumulative();
+
+ Cumulative() {}
+
+ /**
+ * Constructs a cumulative {@code AggregationWindow} that does not have an explicit {@code
+ * Duration}. Instead, cumulative {@code AggregationWindow} always has an interval of infinite
+ * {@code Duration}.
+ *
+ * @return a cumulative {@code AggregationWindow}.
+ * @since 0.8
+ */
+ public static Cumulative create() {
+ return CUMULATIVE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Cumulative, T> p0,
+ Function<? super Interval, T> p1,
+ Function<? super AggregationWindow, T> defaultFunction) {
+ return p0.apply(this);
+ }
+ }
+
+ /**
+ * Interval (finite interval) time {@code AggregationWindow}.
+ *
+ * @since 0.8
+ * @deprecated since 0.13. In the future all {@link View}s will be cumulative.
+ */
+ @Deprecated
+ @Immutable
+ @AutoValue
+ @AutoValue.CopyAnnotations
+ public abstract static class Interval extends AggregationWindow {
+
+ private static final Duration ZERO = Duration.create(0, 0);
+
+ Interval() {}
+
+ /**
+ * Returns the {@code Duration} associated with this {@code Interval}.
+ *
+ * @return a {@code Duration}.
+ * @since 0.8
+ */
+ public abstract Duration getDuration();
+
+ /**
+ * Constructs an interval {@code AggregationWindow} that has a finite explicit {@code
+ * Duration}.
+ *
+ * <p>The {@code Duration} should be able to round to milliseconds. Currently interval window
+ * cannot have smaller {@code Duration} such as microseconds or nanoseconds.
+ *
+ * @return an interval {@code AggregationWindow}.
+ * @since 0.8
+ */
+ public static Interval create(Duration duration) {
+ Utils.checkArgument(duration.compareTo(ZERO) > 0, "Duration must be positive");
+ return new AutoValue_View_AggregationWindow_Interval(duration);
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Cumulative, T> p0,
+ Function<? super Interval, T> p1,
+ Function<? super AggregationWindow, T> defaultFunction) {
+ return p1.apply(this);
+ }
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/stats/ViewData.java b/api/src/main/java/io/opencensus/stats/ViewData.java
new file mode 100644
index 00000000..df6edaa7
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/ViewData.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Duration;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.stats.Aggregation.Count;
+import io.opencensus.stats.Aggregation.Distribution;
+import io.opencensus.stats.Aggregation.LastValue;
+import io.opencensus.stats.Aggregation.Sum;
+import io.opencensus.stats.AggregationData.CountData;
+import io.opencensus.stats.AggregationData.DistributionData;
+import io.opencensus.stats.AggregationData.LastValueDataDouble;
+import io.opencensus.stats.AggregationData.LastValueDataLong;
+import io.opencensus.stats.AggregationData.SumDataDouble;
+import io.opencensus.stats.AggregationData.SumDataLong;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Measure.MeasureLong;
+import io.opencensus.tags.TagValue;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.annotation.concurrent.Immutable;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * The aggregated data for a particular {@link View}.
+ *
+ * @since 0.8
+ */
+@Immutable
+@AutoValue
+@AutoValue.CopyAnnotations
+@SuppressWarnings("deprecation")
+public abstract class ViewData {
+
+ // Prevents this class from being subclassed anywhere else.
+ ViewData() {}
+
+ /**
+ * The {@link View} associated with this {@link ViewData}.
+ *
+ * @since 0.8
+ */
+ public abstract View getView();
+
+ /**
+ * The {@link AggregationData} grouped by combination of tag values, associated with this {@link
+ * ViewData}.
+ *
+ * @since 0.8
+ */
+ public abstract Map<List</*@Nullable*/ TagValue>, AggregationData> getAggregationMap();
+
+ /**
+ * Returns the {@link AggregationWindowData} associated with this {@link ViewData}.
+ *
+ * <p>{@link AggregationWindowData} is deprecated since 0.13, please avoid using this method. Use
+ * {@link #getStart()} and {@link #getEnd()} instead.
+ *
+ * @return the {@code AggregationWindowData}.
+ * @since 0.8
+ * @deprecated in favor of {@link #getStart()} and {@link #getEnd()}.
+ */
+ @Deprecated
+ public abstract AggregationWindowData getWindowData();
+
+ /**
+ * Returns the start {@code Timestamp} for a {@link ViewData}.
+ *
+ * @return the start {@code Timestamp}.
+ * @since 0.13
+ */
+ public abstract Timestamp getStart();
+
+ /**
+ * Returns the end {@code Timestamp} for a {@link ViewData}.
+ *
+ * @return the end {@code Timestamp}.
+ * @since 0.13
+ */
+ public abstract Timestamp getEnd();
+
+ /**
+ * Constructs a new {@link ViewData}.
+ *
+ * @param view the {@link View} associated with this {@link ViewData}.
+ * @param map the mapping from {@link TagValue} list to {@link AggregationData}.
+ * @param windowData the {@link AggregationWindowData}.
+ * @return a {@code ViewData}.
+ * @throws IllegalArgumentException if the types of {@code Aggregation} and {@code
+ * AggregationData} don't match, or the types of {@code Window} and {@code WindowData} don't
+ * match.
+ * @since 0.8
+ * @deprecated in favor of {@link #create(View, Map, Timestamp, Timestamp)}.
+ */
+ @Deprecated
+ public static ViewData create(
+ final View view,
+ Map<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> map,
+ final AggregationWindowData windowData) {
+ checkWindow(view.getWindow(), windowData);
+ final Map<List</*@Nullable*/ TagValue>, AggregationData> deepCopy =
+ new HashMap<List</*@Nullable*/ TagValue>, AggregationData>();
+ for (Entry<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> entry :
+ map.entrySet()) {
+ checkAggregation(view.getAggregation(), entry.getValue(), view.getMeasure());
+ deepCopy.put(
+ Collections.unmodifiableList(new ArrayList</*@Nullable*/ TagValue>(entry.getKey())),
+ entry.getValue());
+ }
+ return windowData.match(
+ new Function<ViewData.AggregationWindowData.CumulativeData, ViewData>() {
+ @Override
+ public ViewData apply(ViewData.AggregationWindowData.CumulativeData arg) {
+ return createInternal(
+ view, Collections.unmodifiableMap(deepCopy), arg, arg.getStart(), arg.getEnd());
+ }
+ },
+ new Function<ViewData.AggregationWindowData.IntervalData, ViewData>() {
+ @Override
+ public ViewData apply(ViewData.AggregationWindowData.IntervalData arg) {
+ Duration duration = ((View.AggregationWindow.Interval) view.getWindow()).getDuration();
+ return createInternal(
+ view,
+ Collections.unmodifiableMap(deepCopy),
+ arg,
+ arg.getEnd()
+ .addDuration(Duration.create(-duration.getSeconds(), -duration.getNanos())),
+ arg.getEnd());
+ }
+ },
+ Functions.<ViewData>throwAssertionError());
+ }
+
+ /**
+ * Constructs a new {@link ViewData}.
+ *
+ * @param view the {@link View} associated with this {@link ViewData}.
+ * @param map the mapping from {@link TagValue} list to {@link AggregationData}.
+ * @param start the start {@link Timestamp} for this {@link ViewData}.
+ * @param end the end {@link Timestamp} for this {@link ViewData}.
+ * @return a {@code ViewData}.
+ * @throws IllegalArgumentException if the types of {@code Aggregation} and {@code
+ * AggregationData} don't match.
+ * @since 0.13
+ */
+ public static ViewData create(
+ View view,
+ Map<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> map,
+ Timestamp start,
+ Timestamp end) {
+ Map<List</*@Nullable*/ TagValue>, AggregationData> deepCopy =
+ new HashMap<List</*@Nullable*/ TagValue>, AggregationData>();
+ for (Entry<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> entry :
+ map.entrySet()) {
+ checkAggregation(view.getAggregation(), entry.getValue(), view.getMeasure());
+ deepCopy.put(
+ Collections.unmodifiableList(new ArrayList</*@Nullable*/ TagValue>(entry.getKey())),
+ entry.getValue());
+ }
+ return createInternal(
+ view,
+ Collections.unmodifiableMap(deepCopy),
+ AggregationWindowData.CumulativeData.create(start, end),
+ start,
+ end);
+ }
+
+ // Suppresses a nullness warning about calls to the AutoValue_ViewData constructor. The generated
+ // constructor does not have the @Nullable annotation on TagValue.
+ private static ViewData createInternal(
+ View view,
+ Map<List</*@Nullable*/ TagValue>, AggregationData> aggregationMap,
+ AggregationWindowData window,
+ Timestamp start,
+ Timestamp end) {
+ @SuppressWarnings("nullness")
+ Map<List<TagValue>, AggregationData> map = aggregationMap;
+ return new AutoValue_ViewData(view, map, window, start, end);
+ }
+
+ private static void checkWindow(
+ View.AggregationWindow window, final AggregationWindowData windowData) {
+ window.match(
+ new Function<View.AggregationWindow.Cumulative, Void>() {
+ @Override
+ public Void apply(View.AggregationWindow.Cumulative arg) {
+ throwIfWindowMismatch(
+ windowData instanceof AggregationWindowData.CumulativeData, arg, windowData);
+ return null;
+ }
+ },
+ new Function<View.AggregationWindow.Interval, Void>() {
+ @Override
+ public Void apply(View.AggregationWindow.Interval arg) {
+ throwIfWindowMismatch(
+ windowData instanceof AggregationWindowData.IntervalData, arg, windowData);
+ return null;
+ }
+ },
+ Functions.</*@Nullable*/ Void>throwAssertionError());
+ }
+
+ private static void throwIfWindowMismatch(
+ boolean isValid, View.AggregationWindow window, AggregationWindowData windowData) {
+ if (!isValid) {
+ throw new IllegalArgumentException(createErrorMessageForWindow(window, windowData));
+ }
+ }
+
+ private static String createErrorMessageForWindow(
+ View.AggregationWindow window, AggregationWindowData windowData) {
+ return "AggregationWindow and AggregationWindowData types mismatch. "
+ + "AggregationWindow: "
+ + window.getClass().getSimpleName()
+ + " AggregationWindowData: "
+ + windowData.getClass().getSimpleName();
+ }
+
+ private static void checkAggregation(
+ final Aggregation aggregation, final AggregationData aggregationData, final Measure measure) {
+ aggregation.match(
+ new Function<Sum, Void>() {
+ @Override
+ public Void apply(Sum arg) {
+ measure.match(
+ new Function<MeasureDouble, Void>() {
+ @Override
+ public Void apply(MeasureDouble arg) {
+ throwIfAggregationMismatch(
+ aggregationData instanceof SumDataDouble, aggregation, aggregationData);
+ return null;
+ }
+ },
+ new Function<MeasureLong, Void>() {
+ @Override
+ public Void apply(MeasureLong arg) {
+ throwIfAggregationMismatch(
+ aggregationData instanceof SumDataLong, aggregation, aggregationData);
+ return null;
+ }
+ },
+ Functions.</*@Nullable*/ Void>throwAssertionError());
+ return null;
+ }
+ },
+ new Function<Count, Void>() {
+ @Override
+ public Void apply(Count arg) {
+ throwIfAggregationMismatch(
+ aggregationData instanceof CountData, aggregation, aggregationData);
+ return null;
+ }
+ },
+ new Function<Distribution, Void>() {
+ @Override
+ public Void apply(Distribution arg) {
+ throwIfAggregationMismatch(
+ aggregationData instanceof DistributionData, aggregation, aggregationData);
+ return null;
+ }
+ },
+ new Function<LastValue, Void>() {
+ @Override
+ public Void apply(LastValue arg) {
+ measure.match(
+ new Function<MeasureDouble, Void>() {
+ @Override
+ public Void apply(MeasureDouble arg) {
+ throwIfAggregationMismatch(
+ aggregationData instanceof LastValueDataDouble,
+ aggregation,
+ aggregationData);
+ return null;
+ }
+ },
+ new Function<MeasureLong, Void>() {
+ @Override
+ public Void apply(MeasureLong arg) {
+ throwIfAggregationMismatch(
+ aggregationData instanceof LastValueDataLong, aggregation, aggregationData);
+ return null;
+ }
+ },
+ Functions.</*@Nullable*/ Void>throwAssertionError());
+ return null;
+ }
+ },
+ new Function<Aggregation, Void>() {
+ @Override
+ public Void apply(Aggregation arg) {
+ // TODO(songya): remove this once Mean aggregation is completely removed. Before that
+ // we need to continue supporting Mean, since it could still be used by users and some
+ // deprecated RPC views.
+ if (arg instanceof Aggregation.Mean) {
+ throwIfAggregationMismatch(
+ aggregationData instanceof AggregationData.MeanData,
+ aggregation,
+ aggregationData);
+ return null;
+ }
+ throw new AssertionError();
+ }
+ });
+ }
+
+ private static void throwIfAggregationMismatch(
+ boolean isValid, Aggregation aggregation, AggregationData aggregationData) {
+ if (!isValid) {
+ throw new IllegalArgumentException(
+ createErrorMessageForAggregation(aggregation, aggregationData));
+ }
+ }
+
+ private static String createErrorMessageForAggregation(
+ Aggregation aggregation, AggregationData aggregationData) {
+ return "Aggregation and AggregationData types mismatch. "
+ + "Aggregation: "
+ + aggregation.getClass().getSimpleName()
+ + " AggregationData: "
+ + aggregationData.getClass().getSimpleName();
+ }
+
+ /**
+ * The {@code AggregationWindowData} for a {@link ViewData}.
+ *
+ * @since 0.8
+ * @deprecated since 0.13, please use start and end {@link Timestamp} instead.
+ */
+ @Deprecated
+ @Immutable
+ public abstract static class AggregationWindowData {
+
+ private AggregationWindowData() {}
+
+ /**
+ * Applies the given match function to the underlying data type.
+ *
+ * @since 0.8
+ */
+ public abstract <T> T match(
+ Function<? super CumulativeData, T> p0,
+ Function<? super IntervalData, T> p1,
+ Function<? super AggregationWindowData, T> defaultFunction);
+
+ /**
+ * Cumulative {@code AggregationWindowData}.
+ *
+ * @since 0.8
+ * @deprecated since 0.13, please use start and end {@link Timestamp} instead.
+ */
+ @Deprecated
+ @Immutable
+ @AutoValue
+ @AutoValue.CopyAnnotations
+ public abstract static class CumulativeData extends AggregationWindowData {
+
+ CumulativeData() {}
+
+ /**
+ * Returns the start {@code Timestamp} for a {@link CumulativeData}.
+ *
+ * @return the start {@code Timestamp}.
+ * @since 0.8
+ */
+ public abstract Timestamp getStart();
+
+ /**
+ * Returns the end {@code Timestamp} for a {@link CumulativeData}.
+ *
+ * @return the end {@code Timestamp}.
+ * @since 0.8
+ */
+ public abstract Timestamp getEnd();
+
+ @Override
+ public final <T> T match(
+ Function<? super CumulativeData, T> p0,
+ Function<? super IntervalData, T> p1,
+ Function<? super AggregationWindowData, T> defaultFunction) {
+ return p0.apply(this);
+ }
+
+ /**
+ * Constructs a new {@link CumulativeData}.
+ *
+ * @since 0.8
+ */
+ public static CumulativeData create(Timestamp start, Timestamp end) {
+ if (start.compareTo(end) > 0) {
+ throw new IllegalArgumentException("Start time is later than end time.");
+ }
+ return new AutoValue_ViewData_AggregationWindowData_CumulativeData(start, end);
+ }
+ }
+
+ /**
+ * Interval {@code AggregationWindowData}.
+ *
+ * @since 0.8
+ * @deprecated since 0.13, please use start and end {@link Timestamp} instead.
+ */
+ @Deprecated
+ @Immutable
+ @AutoValue
+ @AutoValue.CopyAnnotations
+ public abstract static class IntervalData extends AggregationWindowData {
+
+ IntervalData() {}
+
+ /**
+ * Returns the end {@code Timestamp} for an {@link IntervalData}.
+ *
+ * @return the end {@code Timestamp}.
+ * @since 0.8
+ */
+ public abstract Timestamp getEnd();
+
+ @Override
+ public final <T> T match(
+ Function<? super CumulativeData, T> p0,
+ Function<? super IntervalData, T> p1,
+ Function<? super AggregationWindowData, T> defaultFunction) {
+ return p1.apply(this);
+ }
+
+ /**
+ * Constructs a new {@link IntervalData}.
+ *
+ * @since 0.8
+ */
+ public static IntervalData create(Timestamp end) {
+ return new AutoValue_ViewData_AggregationWindowData_IntervalData(end);
+ }
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/stats/ViewManager.java b/api/src/main/java/io/opencensus/stats/ViewManager.java
new file mode 100644
index 00000000..a00165cc
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/ViewManager.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/**
+ * Provides facilities to register {@link View}s for collecting stats and retrieving stats data as a
+ * {@link ViewData}.
+ *
+ * @since 0.8
+ */
+public abstract class ViewManager {
+ /**
+ * Pull model for stats. Registers a {@link View} that will collect data to be accessed via {@link
+ * #getView(View.Name)}.
+ *
+ * @param view the {@code View} to be registered.
+ * @since 0.8
+ */
+ public abstract void registerView(View view);
+
+ /**
+ * Returns the current stats data, {@link ViewData}, associated with the given view name.
+ *
+ * <p>Returns {@code null} if the {@code View} is not registered.
+ *
+ * @param view the name of {@code View} for the current stats.
+ * @return {@code ViewData} for the {@code View}, or {@code null} if the {@code View} is not
+ * registered.
+ * @since 0.8
+ */
+ @Nullable
+ public abstract ViewData getView(View.Name view);
+
+ /**
+ * Returns all registered views that should be exported.
+ *
+ * <p>This method should be used by any stats exporter that automatically exports data for views
+ * registered with the {@link ViewManager}.
+ *
+ * @return all registered views that should be exported.
+ * @since 0.9
+ */
+ public abstract Set<View> getAllExportedViews();
+}
diff --git a/api/src/main/java/io/opencensus/stats/package-info.java b/api/src/main/java/io/opencensus/stats/package-info.java
new file mode 100644
index 00000000..981daa0e
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/** API for stats recording. */
+// TODO: Add more details.
+// TODO: Add code examples.
+package io.opencensus.stats;
diff --git a/api/src/main/java/io/opencensus/tags/InternalUtils.java b/api/src/main/java/io/opencensus/tags/InternalUtils.java
new file mode 100644
index 00000000..944122e1
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/InternalUtils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import java.util.Iterator;
+
+/**
+ * Internal tagging utilities.
+ *
+ * @since 0.8
+ */
+@io.opencensus.common.Internal
+public final class InternalUtils {
+ private InternalUtils() {}
+
+ /**
+ * Internal tag accessor.
+ *
+ * @since 0.8
+ */
+ public static Iterator<Tag> getTags(TagContext tags) {
+ return tags.getIterator();
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/NoopTags.java b/api/src/main/java/io/opencensus/tags/NoopTags.java
new file mode 100644
index 00000000..fb52b164
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/NoopTags.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import io.opencensus.common.Scope;
+import io.opencensus.internal.NoopScope;
+import io.opencensus.internal.Utils;
+import io.opencensus.tags.propagation.TagContextBinarySerializer;
+import io.opencensus.tags.propagation.TagPropagationComponent;
+import java.util.Collections;
+import java.util.Iterator;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** No-op implementations of tagging classes. */
+final class NoopTags {
+
+ private NoopTags() {}
+
+ /**
+ * Returns a {@code TagsComponent} that has a no-op implementation for {@link Tagger}.
+ *
+ * @return a {@code TagsComponent} that has a no-op implementation for {@code Tagger}.
+ */
+ static TagsComponent newNoopTagsComponent() {
+ return new NoopTagsComponent();
+ }
+
+ /**
+ * Returns a {@code Tagger} that only produces {@link TagContext}s with no tags.
+ *
+ * @return a {@code Tagger} that only produces {@code TagContext}s with no tags.
+ */
+ static Tagger getNoopTagger() {
+ return NoopTagger.INSTANCE;
+ }
+
+ /**
+ * Returns a {@code TagContextBuilder} that ignores all calls to {@link TagContextBuilder#put}.
+ *
+ * @return a {@code TagContextBuilder} that ignores all calls to {@link TagContextBuilder#put}.
+ */
+ static TagContextBuilder getNoopTagContextBuilder() {
+ return NoopTagContextBuilder.INSTANCE;
+ }
+
+ /**
+ * Returns a {@code TagContext} that does not contain any tags.
+ *
+ * @return a {@code TagContext} that does not contain any tags.
+ */
+ static TagContext getNoopTagContext() {
+ return NoopTagContext.INSTANCE;
+ }
+
+ /** Returns a {@code TagPropagationComponent} that contains no-op serializers. */
+ static TagPropagationComponent getNoopTagPropagationComponent() {
+ return NoopTagPropagationComponent.INSTANCE;
+ }
+
+ /**
+ * Returns a {@code TagContextBinarySerializer} that serializes all {@code TagContext}s to zero
+ * bytes and deserializes all inputs to empty {@code TagContext}s.
+ */
+ static TagContextBinarySerializer getNoopTagContextBinarySerializer() {
+ return NoopTagContextBinarySerializer.INSTANCE;
+ }
+
+ @ThreadSafe
+ private static final class NoopTagsComponent extends TagsComponent {
+ private volatile boolean isRead;
+
+ @Override
+ public Tagger getTagger() {
+ return getNoopTagger();
+ }
+
+ @Override
+ public TagPropagationComponent getTagPropagationComponent() {
+ return getNoopTagPropagationComponent();
+ }
+
+ @Override
+ public TaggingState getState() {
+ isRead = true;
+ return TaggingState.DISABLED;
+ }
+
+ @Override
+ @Deprecated
+ public void setState(TaggingState state) {
+ Utils.checkNotNull(state, "state");
+ Utils.checkState(!isRead, "State was already read, cannot set state.");
+ }
+ }
+
+ @Immutable
+ private static final class NoopTagger extends Tagger {
+ static final Tagger INSTANCE = new NoopTagger();
+
+ @Override
+ public TagContext empty() {
+ return getNoopTagContext();
+ }
+
+ @Override
+ public TagContext getCurrentTagContext() {
+ return getNoopTagContext();
+ }
+
+ @Override
+ public TagContextBuilder emptyBuilder() {
+ return getNoopTagContextBuilder();
+ }
+
+ @Override
+ public TagContextBuilder toBuilder(TagContext tags) {
+ Utils.checkNotNull(tags, "tags");
+ return getNoopTagContextBuilder();
+ }
+
+ @Override
+ public TagContextBuilder currentBuilder() {
+ return getNoopTagContextBuilder();
+ }
+
+ @Override
+ public Scope withTagContext(TagContext tags) {
+ Utils.checkNotNull(tags, "tags");
+ return NoopScope.getInstance();
+ }
+ }
+
+ @Immutable
+ private static final class NoopTagContextBuilder extends TagContextBuilder {
+ static final TagContextBuilder INSTANCE = new NoopTagContextBuilder();
+
+ @Override
+ public TagContextBuilder put(TagKey key, TagValue value) {
+ Utils.checkNotNull(key, "key");
+ Utils.checkNotNull(value, "value");
+ return this;
+ }
+
+ @Override
+ public TagContextBuilder remove(TagKey key) {
+ Utils.checkNotNull(key, "key");
+ return this;
+ }
+
+ @Override
+ public TagContext build() {
+ return getNoopTagContext();
+ }
+
+ @Override
+ public Scope buildScoped() {
+ return NoopScope.getInstance();
+ }
+ }
+
+ @Immutable
+ private static final class NoopTagContext extends TagContext {
+ static final TagContext INSTANCE = new NoopTagContext();
+
+ // TODO(sebright): Is there any way to let the user know that their tags were ignored?
+ @Override
+ protected Iterator<Tag> getIterator() {
+ return Collections.<Tag>emptySet().iterator();
+ }
+ }
+
+ @Immutable
+ private static final class NoopTagPropagationComponent extends TagPropagationComponent {
+ static final TagPropagationComponent INSTANCE = new NoopTagPropagationComponent();
+
+ @Override
+ public TagContextBinarySerializer getBinarySerializer() {
+ return getNoopTagContextBinarySerializer();
+ }
+ }
+
+ @Immutable
+ private static final class NoopTagContextBinarySerializer extends TagContextBinarySerializer {
+ static final TagContextBinarySerializer INSTANCE = new NoopTagContextBinarySerializer();
+ static final byte[] EMPTY_BYTE_ARRAY = {};
+
+ @Override
+ public byte[] toByteArray(TagContext tags) {
+ Utils.checkNotNull(tags, "tags");
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ @Override
+ public TagContext fromByteArray(byte[] bytes) {
+ Utils.checkNotNull(bytes, "bytes");
+ return getNoopTagContext();
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/Tag.java b/api/src/main/java/io/opencensus/tags/Tag.java
new file mode 100644
index 00000000..9e0a7a82
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/Tag.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import com.google.auto.value.AutoValue;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * {@link TagKey} paired with a {@link TagValue}.
+ *
+ * @since 0.8
+ */
+@Immutable
+@AutoValue
+public abstract class Tag {
+
+ Tag() {}
+
+ /**
+ * Creates a {@code Tag} from the given key and value.
+ *
+ * @param key the tag key.
+ * @param value the tag value.
+ * @return a {@code Tag} with the given key and value.
+ * @since 0.8
+ */
+ public static Tag create(TagKey key, TagValue value) {
+ return new AutoValue_Tag(key, value);
+ }
+
+ /**
+ * Returns the tag's key.
+ *
+ * @return the tag's key.
+ * @since 0.8
+ */
+ public abstract TagKey getKey();
+
+ /**
+ * Returns the tag's value.
+ *
+ * @return the tag's value.
+ * @since 0.8
+ */
+ public abstract TagValue getValue();
+}
diff --git a/api/src/main/java/io/opencensus/tags/TagContext.java b/api/src/main/java/io/opencensus/tags/TagContext.java
new file mode 100644
index 00000000..e36acdff
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagContext.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A map from {@link TagKey} to {@link TagValue} that can be used to label anything that is
+ * associated with a specific operation.
+ *
+ * <p>For example, {@code TagContext}s can be used to label stats, log messages, or debugging
+ * information.
+ *
+ * @since 0.8
+ */
+@Immutable
+public abstract class TagContext {
+
+ /**
+ * Returns an iterator over the tags in this {@code TagContext}.
+ *
+ * @return an iterator over the tags in this {@code TagContext}.
+ * @since 0.8
+ */
+ // This method is protected to prevent client code from accessing the tags of any TagContext. We
+ // don't currently support efficient access to tags. However, every TagContext subclass needs to
+ // provide access to its tags to the stats and tagging implementations by implementing this
+ // method. If we decide to support access to tags in the future, we can add a public iterator()
+ // method and implement it for all subclasses by calling getIterator().
+ //
+ // The stats and tagging implementations can access any TagContext's tags through
+ // io.opencensus.tags.InternalUtils.getTags, which calls this method.
+ protected abstract Iterator<Tag> getIterator();
+
+ @Override
+ public String toString() {
+ return "TagContext";
+ }
+
+ /**
+ * Returns true iff the other object is an instance of {@code TagContext} and contains the same
+ * key-value pairs. Implementations are free to override this method to provide better
+ * performance.
+ */
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof TagContext)) {
+ return false;
+ }
+ TagContext otherTags = (TagContext) other;
+ Iterator<Tag> iter1 = getIterator();
+ Iterator<Tag> iter2 = otherTags.getIterator();
+ HashMap<Tag, Integer> tags = new HashMap<Tag, Integer>();
+ while (iter1 != null && iter1.hasNext()) {
+ Tag tag = iter1.next();
+ if (tags.containsKey(tag)) {
+ tags.put(tag, tags.get(tag) + 1);
+ } else {
+ tags.put(tag, 1);
+ }
+ }
+ while (iter2 != null && iter2.hasNext()) {
+ Tag tag = iter2.next();
+ if (!tags.containsKey(tag)) {
+ return false;
+ }
+ int count = tags.get(tag);
+ if (count > 1) {
+ tags.put(tag, count - 1);
+ } else {
+ tags.remove(tag);
+ }
+ }
+ return tags.isEmpty();
+ }
+
+ @Override
+ public final int hashCode() {
+ int hashCode = 0;
+ Iterator<Tag> i = getIterator();
+ if (i == null) {
+ return hashCode;
+ }
+ while (i.hasNext()) {
+ Tag tag = i.next();
+ if (tag != null) {
+ hashCode += tag.hashCode();
+ }
+ }
+ return hashCode;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/TagContextBuilder.java b/api/src/main/java/io/opencensus/tags/TagContextBuilder.java
new file mode 100644
index 00000000..f4268968
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagContextBuilder.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import io.opencensus.common.Scope;
+
+/**
+ * Builder for the {@link TagContext} class.
+ *
+ * @since 0.8
+ */
+public abstract class TagContextBuilder {
+
+ /**
+ * Adds the key/value pair regardless of whether the key is present.
+ *
+ * @param key the {@code TagKey} which will be set.
+ * @param value the {@code TagValue} to set for the given key.
+ * @return this
+ * @since 0.8
+ */
+ public abstract TagContextBuilder put(TagKey key, TagValue value);
+
+ /**
+ * Removes the key if it exists.
+ *
+ * @param key the {@code TagKey} which will be removed.
+ * @return this
+ * @since 0.8
+ */
+ public abstract TagContextBuilder remove(TagKey key);
+
+ /**
+ * Creates a {@code TagContext} from this builder.
+ *
+ * @return a {@code TagContext} with the same tags as this builder.
+ * @since 0.8
+ */
+ public abstract TagContext build();
+
+ /**
+ * Enters the scope of code where the {@link TagContext} created from this builder is in the
+ * current context and returns an object that represents that scope. The scope is exited when the
+ * returned object is closed.
+ *
+ * @return an object that defines a scope where the {@code TagContext} created from this builder
+ * is set to the current context.
+ * @since 0.8
+ */
+ public abstract Scope buildScoped();
+}
diff --git a/api/src/main/java/io/opencensus/tags/TagKey.java b/api/src/main/java/io/opencensus/tags/TagKey.java
new file mode 100644
index 00000000..ca4582bd
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagKey.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.StringUtils;
+import io.opencensus.internal.Utils;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A key to a value stored in a {@link TagContext}.
+ *
+ * <p>Each {@code TagKey} has a {@code String} name. Names have a maximum length of {@link
+ * #MAX_LENGTH} and contain only printable ASCII characters.
+ *
+ * <p>{@code TagKey}s are designed to be used as constants. Declaring each key as a constant
+ * prevents key names from being validated multiple times.
+ *
+ * @since 0.8
+ */
+@Immutable
+@AutoValue
+public abstract class TagKey {
+ /**
+ * The maximum length for a tag key name. The value is {@value #MAX_LENGTH}.
+ *
+ * @since 0.8
+ */
+ public static final int MAX_LENGTH = 255;
+
+ TagKey() {}
+
+ /**
+ * Constructs a {@code TagKey} with the given name.
+ *
+ * <p>The name must meet the following requirements:
+ *
+ * <ol>
+ * <li>It cannot be longer than {@link #MAX_LENGTH}.
+ * <li>It can only contain printable ASCII characters.
+ * </ol>
+ *
+ * @param name the name of the key.
+ * @return a {@code TagKey} with the given name.
+ * @throws IllegalArgumentException if the name is not valid.
+ * @since 0.8
+ */
+ public static TagKey create(String name) {
+ Utils.checkArgument(isValid(name), "Invalid TagKey name: %s", name);
+ return new AutoValue_TagKey(name);
+ }
+
+ /**
+ * Returns the name of the key.
+ *
+ * @return the name of the key.
+ * @since 0.8
+ */
+ public abstract String getName();
+
+ /**
+ * Determines whether the given {@code String} is a valid tag key.
+ *
+ * @param name the tag key name to be validated.
+ * @return whether the name is valid.
+ */
+ private static boolean isValid(String name) {
+ return !name.isEmpty() && name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/TagValue.java b/api/src/main/java/io/opencensus/tags/TagValue.java
new file mode 100644
index 00000000..9111ca28
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagValue.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.StringUtils;
+import io.opencensus.internal.Utils;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A validated tag value.
+ *
+ * <p>Validation ensures that the {@code String} has a maximum length of {@link #MAX_LENGTH} and
+ * contains only printable ASCII characters.
+ *
+ * @since 0.8
+ */
+@Immutable
+@AutoValue
+public abstract class TagValue {
+ /**
+ * The maximum length for a tag value. The value is {@value #MAX_LENGTH}.
+ *
+ * @since 0.8
+ */
+ public static final int MAX_LENGTH = 255;
+
+ TagValue() {}
+
+ /**
+ * Constructs a {@code TagValue} from the given string. The string must meet the following
+ * requirements:
+ *
+ * <ol>
+ * <li>It cannot be longer than {@link #MAX_LENGTH}.
+ * <li>It can only contain printable ASCII characters.
+ * </ol>
+ *
+ * @param value the tag value.
+ * @throws IllegalArgumentException if the {@code String} is not valid.
+ * @since 0.8
+ */
+ public static TagValue create(String value) {
+ Utils.checkArgument(isValid(value), "Invalid TagValue: %s", value);
+ return new AutoValue_TagValue(value);
+ }
+
+ /**
+ * Returns the tag value as a {@code String}.
+ *
+ * @return the tag value as a {@code String}.
+ * @since 0.8
+ */
+ public abstract String asString();
+
+ /**
+ * Determines whether the given {@code String} is a valid tag value.
+ *
+ * @param value the tag value to be validated.
+ * @return whether the value is valid.
+ */
+ private static boolean isValid(String value) {
+ return value.length() <= MAX_LENGTH && StringUtils.isPrintableString(value);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/Tagger.java b/api/src/main/java/io/opencensus/tags/Tagger.java
new file mode 100644
index 00000000..f1e203ad
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/Tagger.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import io.opencensus.common.Scope;
+
+/**
+ * Object for creating new {@link TagContext}s and {@code TagContext}s based on the current context.
+ *
+ * <p>This class returns {@link TagContextBuilder builders} that can be used to create the
+ * implementation-dependent {@link TagContext}s.
+ *
+ * <p>Implementations may have different constraints and are free to convert tag contexts to their
+ * own subtypes. This means callers cannot assume the {@link #getCurrentTagContext() current
+ * context} is the same instance as the one {@link #withTagContext(TagContext) placed into scope}.
+ *
+ * @since 0.8
+ */
+public abstract class Tagger {
+
+ /**
+ * Returns an empty {@code TagContext}.
+ *
+ * @return an empty {@code TagContext}.
+ * @since 0.8
+ */
+ public abstract TagContext empty();
+
+ /**
+ * Returns the current {@code TagContext}.
+ *
+ * @return the current {@code TagContext}.
+ * @since 0.8
+ */
+ public abstract TagContext getCurrentTagContext();
+
+ /**
+ * Returns a new empty {@code Builder}.
+ *
+ * @return a new empty {@code Builder}.
+ * @since 0.8
+ */
+ public abstract TagContextBuilder emptyBuilder();
+
+ /**
+ * Returns a builder based on this {@code TagContext}.
+ *
+ * @return a builder based on this {@code TagContext}.
+ * @since 0.8
+ */
+ public abstract TagContextBuilder toBuilder(TagContext tags);
+
+ /**
+ * Returns a new builder created from the current {@code TagContext}.
+ *
+ * @return a new builder created from the current {@code TagContext}.
+ * @since 0.8
+ */
+ public abstract TagContextBuilder currentBuilder();
+
+ /**
+ * Enters the scope of code where the given {@code TagContext} is in the current context
+ * (replacing the previous {@code TagContext}) and returns an object that represents that scope.
+ * The scope is exited when the returned object is closed.
+ *
+ * @param tags the {@code TagContext} to be set to the current context.
+ * @return an object that defines a scope where the given {@code TagContext} is set to the current
+ * context.
+ * @since 0.8
+ */
+ public abstract Scope withTagContext(TagContext tags);
+}
diff --git a/api/src/main/java/io/opencensus/tags/TaggingState.java b/api/src/main/java/io/opencensus/tags/TaggingState.java
new file mode 100644
index 00000000..88970361
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TaggingState.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017, 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.tags;
+
+/**
+ * State of the {@link TagsComponent}.
+ *
+ * @since 0.8
+ */
+public enum TaggingState {
+ // TODO(sebright): Should we add a state that propagates the tags, but doesn't allow
+ // modifications?
+
+ /**
+ * State that fully enables tagging.
+ *
+ * <p>The {@link TagsComponent} can add tags to {@link TagContext}s, propagate {@code TagContext}s
+ * in the current context, and serialize {@code TagContext}s.
+ *
+ * @since 0.8
+ */
+ ENABLED,
+
+ /**
+ * State that disables tagging.
+ *
+ * <p>The {@link TagsComponent} may not add tags to {@link TagContext}s, propagate {@code
+ * TagContext}s in the current context, or serialize {@code TagContext}s.
+ *
+ * @since 0.8
+ */
+ // TODO(sebright): Document how this interacts with stats collection.
+ DISABLED
+}
diff --git a/api/src/main/java/io/opencensus/tags/Tags.java b/api/src/main/java/io/opencensus/tags/Tags.java
new file mode 100644
index 00000000..07123647
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/Tags.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import io.opencensus.internal.DefaultVisibilityForTesting;
+import io.opencensus.internal.Provider;
+import io.opencensus.tags.propagation.TagPropagationComponent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Class for accessing the default {@link TagsComponent}.
+ *
+ * @since 0.8
+ */
+public final class Tags {
+ private static final Logger logger = Logger.getLogger(Tags.class.getName());
+
+ private static final TagsComponent tagsComponent =
+ loadTagsComponent(TagsComponent.class.getClassLoader());
+
+ private Tags() {}
+
+ /**
+ * Returns the default {@code Tagger}.
+ *
+ * @return the default {@code Tagger}.
+ * @since 0.8
+ */
+ public static Tagger getTagger() {
+ return tagsComponent.getTagger();
+ }
+
+ /**
+ * Returns the default {@code TagPropagationComponent}.
+ *
+ * @return the default {@code TagPropagationComponent}.
+ * @since 0.8
+ */
+ public static TagPropagationComponent getTagPropagationComponent() {
+ return tagsComponent.getTagPropagationComponent();
+ }
+
+ /**
+ * Returns the current {@code TaggingState}.
+ *
+ * <p>When no implementation is available, {@code getState} always returns {@link
+ * TaggingState#DISABLED}.
+ *
+ * <p>Once {@link #getState()} is called, subsequent calls to {@link #setState(TaggingState)} will
+ * throw an {@code IllegalStateException}.
+ *
+ * @return the current {@code TaggingState}.
+ * @since 0.8
+ */
+ public static TaggingState getState() {
+ return tagsComponent.getState();
+ }
+
+ /**
+ * Sets the current {@code TaggingState}.
+ *
+ * <p>When no implementation is available, {@code setState} does not change the state.
+ *
+ * @param state the new {@code TaggingState}.
+ * @throws IllegalStateException if {@link #getState()} was previously called.
+ * @deprecated This method is deprecated because other libraries could cache the result of {@link
+ * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in
+ * initialization. This method throws {@link IllegalStateException} after {@link #getState()}
+ * has been called, in order to limit changes to the result of {@code getState()}.
+ * @since 0.8
+ */
+ @Deprecated
+ public static void setState(TaggingState state) {
+ tagsComponent.setState(state);
+ }
+
+ // Any provider that may be used for TagsComponent can be added here.
+ @DefaultVisibilityForTesting
+ static TagsComponent loadTagsComponent(@Nullable ClassLoader classLoader) {
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impl.tags.TagsComponentImpl", /*initialize=*/ true, classLoader),
+ TagsComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load full implementation for TagsComponent, now trying to load lite "
+ + "implementation.",
+ e);
+ }
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impllite.tags.TagsComponentImplLite",
+ /*initialize=*/ true,
+ classLoader),
+ TagsComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load lite implementation for TagsComponent, now using "
+ + "default implementation for TagsComponent.",
+ e);
+ }
+ return NoopTags.newNoopTagsComponent();
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/TagsComponent.java b/api/src/main/java/io/opencensus/tags/TagsComponent.java
new file mode 100644
index 00000000..d34f1951
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagsComponent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import io.opencensus.tags.propagation.TagPropagationComponent;
+
+/**
+ * Class that holds the implementation for {@link Tagger} and {@link TagPropagationComponent}.
+ *
+ * <p>All objects returned by methods on {@code TagsComponent} are cacheable.
+ *
+ * @since 0.8
+ */
+public abstract class TagsComponent {
+
+ /**
+ * Returns the {@link Tagger} for this implementation.
+ *
+ * @since 0.8
+ */
+ public abstract Tagger getTagger();
+
+ /**
+ * Returns the {@link TagPropagationComponent} for this implementation.
+ *
+ * @since 0.8
+ */
+ public abstract TagPropagationComponent getTagPropagationComponent();
+
+ /**
+ * Returns the current {@code TaggingState}.
+ *
+ * <p>When no implementation is available, {@code getState} always returns {@link
+ * TaggingState#DISABLED}.
+ *
+ * <p>Once {@link #getState()} is called, subsequent calls to {@link #setState(TaggingState)} will
+ * throw an {@code IllegalStateException}.
+ *
+ * @return the current {@code TaggingState}.
+ * @since 0.8
+ */
+ public abstract TaggingState getState();
+
+ /**
+ * Sets the current {@code TaggingState}.
+ *
+ * <p>When no implementation is available, {@code setState} does not change the state.
+ *
+ * @param state the new {@code TaggingState}.
+ * @throws IllegalStateException if {@link #getState()} was previously called.
+ * @deprecated This method is deprecated because other libraries could cache the result of {@link
+ * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in
+ * initialization. This method throws {@link IllegalStateException} after {@code getState()}
+ * has been called, in order to limit changes to the result of {@code getState()}.
+ * @since 0.8
+ */
+ @Deprecated
+ public abstract void setState(TaggingState state);
+}
diff --git a/api/src/main/java/io/opencensus/tags/package-info.java b/api/src/main/java/io/opencensus/tags/package-info.java
new file mode 100644
index 00000000..eb19ee77
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/package-info.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2017, 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.
+ */
+
+/**
+ * API for associating tags with scoped operations.
+ *
+ * <p>This package manages a set of tags in the {@code io.grpc.Context}. The tags can be used to
+ * label anything that is associated with a specific operation. For example, the {@code
+ * io.opencensus.stats} package labels all stats with the current tags.
+ *
+ * <p>{@link io.opencensus.tags.Tag Tags} are key-value pairs. The {@link io.opencensus.tags.TagKey
+ * keys} and {@link io.opencensus.tags.TagValue values} are wrapped {@code String}s. They are stored
+ * as a map in a {@link io.opencensus.tags.TagContext}.
+ *
+ * <p>Note that tags are independent of the tracing data that is propagated in the {@code
+ * io.grpc.Context}, such as trace ID.
+ */
+// TODO(sebright): Add code examples.
+package io.opencensus.tags;
diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java b/api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java
new file mode 100644
index 00000000..39eb8cee
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017, 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.tags.propagation;
+
+import io.opencensus.tags.TagContext;
+
+/**
+ * Object for serializing and deserializing {@link TagContext}s with the binary format.
+ *
+ * <p>See <a
+ * href="https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md#tag-context">opencensus-specs</a>
+ * for the specification of the cross-language binary serialization format.
+ *
+ * @since 0.8
+ */
+public abstract class TagContextBinarySerializer {
+
+ /**
+ * Serializes the {@code TagContext} into the on-the-wire representation.
+ *
+ * <p>This method should be the inverse of {@link #fromByteArray}.
+ *
+ * @param tags the {@code TagContext} to serialize.
+ * @return the on-the-wire representation of a {@code TagContext}.
+ * @throws TagContextSerializationException if the result would be larger than the maximum allowed
+ * serialized size.
+ * @since 0.8
+ */
+ public abstract byte[] toByteArray(TagContext tags) throws TagContextSerializationException;
+
+ /**
+ * Creates a {@code TagContext} from the given on-the-wire encoded representation.
+ *
+ * <p>This method should be the inverse of {@link #toByteArray}.
+ *
+ * @param bytes on-the-wire representation of a {@code TagContext}.
+ * @return a {@code TagContext} deserialized from {@code bytes}.
+ * @throws TagContextDeserializationException if there is a parse error, the input contains
+ * invalid tags, or the input is larger than the maximum allowed serialized size.
+ * @since 0.8
+ */
+ public abstract TagContext fromByteArray(byte[] bytes) throws TagContextDeserializationException;
+}
diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagContextDeserializationException.java b/api/src/main/java/io/opencensus/tags/propagation/TagContextDeserializationException.java
new file mode 100644
index 00000000..11dcb59f
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextDeserializationException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017, 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.tags.propagation;
+
+import io.opencensus.tags.TagContext;
+
+/**
+ * Exception thrown when a {@link TagContext} cannot be parsed.
+ *
+ * @since 0.8
+ */
+public final class TagContextDeserializationException extends Exception {
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructs a new {@code TagContextParseException} with the given message.
+ *
+ * @param message a message describing the error.
+ * @since 0.8
+ */
+ public TagContextDeserializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code TagContextParseException} with the given message and cause.
+ *
+ * @param message a message describing the error.
+ * @param cause the cause of the error.
+ * @since 0.8
+ */
+ public TagContextDeserializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagContextSerializationException.java b/api/src/main/java/io/opencensus/tags/propagation/TagContextSerializationException.java
new file mode 100644
index 00000000..bb3c9b74
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextSerializationException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017, 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.tags.propagation;
+
+import io.opencensus.tags.TagContext;
+
+/**
+ * Exception thrown when a {@link TagContext} cannot be serialized.
+ *
+ * @since 0.8
+ */
+public final class TagContextSerializationException extends Exception {
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructs a new {@code TagContextSerializationException} with the given message.
+ *
+ * @param message a message describing the error.
+ * @since 0.8
+ */
+ public TagContextSerializationException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code TagContextSerializationException} with the given message and cause.
+ *
+ * @param message a message describing the error.
+ * @param cause the cause of the error.
+ * @since 0.8
+ */
+ public TagContextSerializationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java b/api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java
new file mode 100644
index 00000000..6ececa79
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017, 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.tags.propagation;
+
+import io.opencensus.tags.TagContext;
+
+/**
+ * Object containing all supported {@link TagContext} propagation formats.
+ *
+ * @since 0.8
+ */
+// TODO(sebright): Add an HTTP serializer.
+public abstract class TagPropagationComponent {
+
+ /**
+ * Returns the {@link TagContextBinarySerializer} for this implementation.
+ *
+ * @return the {@code TagContextBinarySerializer} for this implementation.
+ * @since 0.8
+ */
+ public abstract TagContextBinarySerializer getBinarySerializer();
+}
diff --git a/api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java b/api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java
new file mode 100644
index 00000000..8936bbbb
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017, 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.tags.unsafe;
+
+import io.grpc.Context;
+import io.opencensus.tags.Tag;
+import io.opencensus.tags.TagContext;
+import java.util.Collections;
+import java.util.Iterator;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Utility methods for accessing the {@link TagContext} contained in the {@link io.grpc.Context}.
+ *
+ * <p>Most code should interact with the current context via the public APIs in {@link
+ * io.opencensus.tags.TagContext} and avoid accessing {@link #TAG_CONTEXT_KEY} directly.
+ *
+ * @since 0.8
+ */
+public final class ContextUtils {
+ private static final TagContext EMPTY_TAG_CONTEXT = new EmptyTagContext();
+
+ private ContextUtils() {}
+
+ /**
+ * The {@link io.grpc.Context.Key} used to interact with the {@code TagContext} contained in the
+ * {@link io.grpc.Context}.
+ *
+ * @since 0.8
+ */
+ public static final Context.Key<TagContext> TAG_CONTEXT_KEY =
+ Context.keyWithDefault("opencensus-tag-context-key", EMPTY_TAG_CONTEXT);
+
+ @Immutable
+ private static final class EmptyTagContext extends TagContext {
+
+ @Override
+ protected Iterator<Tag> getIterator() {
+ return Collections.<Tag>emptySet().iterator();
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/Annotation.java b/api/src/main/java/io/opencensus/trace/Annotation.java
new file mode 100644
index 00000000..97f2fdd2
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Annotation.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.Utils;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A text annotation with a set of attributes.
+ *
+ * @since 0.5
+ */
+@Immutable
+@AutoValue
+public abstract class Annotation {
+ private static final Map<String, AttributeValue> EMPTY_ATTRIBUTES =
+ Collections.unmodifiableMap(Collections.<String, AttributeValue>emptyMap());
+
+ /**
+ * Returns a new {@code Annotation} with the given description.
+ *
+ * @param description the text description of the {@code Annotation}.
+ * @return a new {@code Annotation} with the given description.
+ * @throws NullPointerException if {@code description} is {@code null}.
+ * @since 0.5
+ */
+ public static Annotation fromDescription(String description) {
+ return new AutoValue_Annotation(description, EMPTY_ATTRIBUTES);
+ }
+
+ /**
+ * Returns a new {@code Annotation} with the given description and set of attributes.
+ *
+ * @param description the text description of the {@code Annotation}.
+ * @param attributes the attributes of the {@code Annotation}.
+ * @return a new {@code Annotation} with the given description and set of attributes.
+ * @throws NullPointerException if {@code description} or {@code attributes} are {@code null}.
+ * @since 0.5
+ */
+ public static Annotation fromDescriptionAndAttributes(
+ String description, Map<String, AttributeValue> attributes) {
+ return new AutoValue_Annotation(
+ description,
+ Collections.unmodifiableMap(
+ new HashMap<String, AttributeValue>(Utils.checkNotNull(attributes, "attributes"))));
+ }
+
+ /**
+ * Return the description of the {@code Annotation}.
+ *
+ * @return the description of the {@code Annotation}.
+ * @since 0.5
+ */
+ public abstract String getDescription();
+
+ /**
+ * Return the attributes of the {@code Annotation}.
+ *
+ * @return the attributes of the {@code Annotation}.
+ * @since 0.5
+ */
+ public abstract Map<String, AttributeValue> getAttributes();
+
+ Annotation() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/AttributeValue.java b/api/src/main/java/io/opencensus/trace/AttributeValue.java
new file mode 100644
index 00000000..efa9d1df
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/AttributeValue.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+import io.opencensus.internal.Utils;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that represents all the possible values for an attribute. An attribute can have 3 types
+ * of values: {@code String}, {@code Boolean} or {@code Long}.
+ *
+ * @since 0.5
+ */
+@Immutable
+public abstract class AttributeValue {
+ /**
+ * Returns an {@code AttributeValue} with a string value.
+ *
+ * @param stringValue The new value.
+ * @return an {@code AttributeValue} with a string value.
+ * @throws NullPointerException if {@code stringValue} is {@code null}.
+ * @since 0.5
+ */
+ public static AttributeValue stringAttributeValue(String stringValue) {
+ return AttributeValueString.create(stringValue);
+ }
+
+ /**
+ * Returns an {@code AttributeValue} with a boolean value.
+ *
+ * @param booleanValue The new value.
+ * @return an {@code AttributeValue} with a boolean value.
+ * @since 0.5
+ */
+ public static AttributeValue booleanAttributeValue(boolean booleanValue) {
+ return AttributeValueBoolean.create(booleanValue);
+ }
+
+ /**
+ * Returns an {@code AttributeValue} with a long value.
+ *
+ * @param longValue The new value.
+ * @return an {@code AttributeValue} with a long value.
+ * @since 0.5
+ */
+ public static AttributeValue longAttributeValue(long longValue) {
+ return AttributeValueLong.create(longValue);
+ }
+
+ /**
+ * Returns an {@code AttributeValue} with a double value.
+ *
+ * @param doubleValue The new value.
+ * @return an {@code AttributeValue} with a double value.
+ * @since 0.17
+ */
+ public static AttributeValue doubleAttributeValue(double doubleValue) {
+ return AttributeValueDouble.create(doubleValue);
+ }
+
+ AttributeValue() {}
+
+ /**
+ * Applies a function to the underlying value. The function that is called depends on the value's
+ * type, which can be {@code String}, {@code Long}, or {@code Boolean}.
+ *
+ * @param stringFunction the function that should be applied if the value has type {@code String}.
+ * @param longFunction the function that should be applied if the value has type {@code Long}.
+ * @param booleanFunction the function that should be applied if the value has type {@code
+ * Boolean}.
+ * @param defaultFunction the function that should be applied if the value has a type that was
+ * added after this {@code match} method was added to the API. See {@link
+ * io.opencensus.common.Functions} for some common functions for handling unknown types.
+ * @return the result of the function applied to the underlying value.
+ * @since 0.5
+ * @deprecated in favor of {@link #match(Function, Function, Function, Function, Function)}.
+ */
+ @Deprecated
+ public abstract <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<Object, T> defaultFunction);
+
+ /**
+ * Applies a function to the underlying value. The function that is called depends on the value's
+ * type, which can be {@code String}, {@code Long}, or {@code Boolean}.
+ *
+ * @param stringFunction the function that should be applied if the value has type {@code String}.
+ * @param longFunction the function that should be applied if the value has type {@code Long}.
+ * @param booleanFunction the function that should be applied if the value has type {@code
+ * Boolean}.
+ * @param doubleFunction the function that should be applied if the value has type {@code Double}.
+ * @param defaultFunction the function that should be applied if the value has a type that was
+ * added after this {@code match} method was added to the API. See {@link
+ * io.opencensus.common.Functions} for some common functions for handling unknown types.
+ * @return the result of the function applied to the underlying value.
+ * @since 0.17
+ */
+ @SuppressWarnings("InconsistentOverloads")
+ public abstract <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Double, T> doubleFunction,
+ Function<Object, T> defaultFunction);
+
+ @Immutable
+ @AutoValue
+ abstract static class AttributeValueString extends AttributeValue {
+
+ AttributeValueString() {}
+
+ static AttributeValue create(String stringValue) {
+ return new AutoValue_AttributeValue_AttributeValueString(
+ Utils.checkNotNull(stringValue, "stringValue"));
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<Object, T> defaultFunction) {
+ return stringFunction.apply(getStringValue());
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Double, T> doubleFunction,
+ Function<Object, T> defaultFunction) {
+ return stringFunction.apply(getStringValue());
+ }
+
+ abstract String getStringValue();
+ }
+
+ @Immutable
+ @AutoValue
+ abstract static class AttributeValueBoolean extends AttributeValue {
+
+ AttributeValueBoolean() {}
+
+ static AttributeValue create(Boolean booleanValue) {
+ return new AutoValue_AttributeValue_AttributeValueBoolean(
+ Utils.checkNotNull(booleanValue, "booleanValue"));
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<Object, T> defaultFunction) {
+ return booleanFunction.apply(getBooleanValue());
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Double, T> doubleFunction,
+ Function<Object, T> defaultFunction) {
+ return booleanFunction.apply(getBooleanValue());
+ }
+
+ abstract Boolean getBooleanValue();
+ }
+
+ @Immutable
+ @AutoValue
+ abstract static class AttributeValueLong extends AttributeValue {
+
+ AttributeValueLong() {}
+
+ static AttributeValue create(Long longValue) {
+ return new AutoValue_AttributeValue_AttributeValueLong(
+ Utils.checkNotNull(longValue, "longValue"));
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<Object, T> defaultFunction) {
+ return longFunction.apply(getLongValue());
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Double, T> doubleFunction,
+ Function<Object, T> defaultFunction) {
+ return longFunction.apply(getLongValue());
+ }
+
+ abstract Long getLongValue();
+ }
+
+ @Immutable
+ @AutoValue
+ abstract static class AttributeValueDouble extends AttributeValue {
+
+ AttributeValueDouble() {}
+
+ static AttributeValue create(Double doubleValue) {
+ return new AutoValue_AttributeValue_AttributeValueDouble(
+ Utils.checkNotNull(doubleValue, "doubleValue"));
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<Object, T> defaultFunction) {
+ return defaultFunction.apply(getDoubleValue());
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super String, T> stringFunction,
+ Function<? super Boolean, T> booleanFunction,
+ Function<? super Long, T> longFunction,
+ Function<? super Double, T> doubleFunction,
+ Function<Object, T> defaultFunction) {
+ return doubleFunction.apply(getDoubleValue());
+ }
+
+ abstract Double getDoubleValue();
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/BaseMessageEvent.java b/api/src/main/java/io/opencensus/trace/BaseMessageEvent.java
new file mode 100644
index 00000000..5ad961f6
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/BaseMessageEvent.java
@@ -0,0 +1,37 @@
+/*
+ * 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.trace;
+
+/**
+ * Superclass for {@link MessageEvent} and {@link NetworkEvent} to resolve API backward
+ * compatibility issue.
+ *
+ * <p>{@code SpanData.create} can't be overloaded with parameter types that differ only in the type
+ * of the TimedEvent, because the signatures are the same after generic type erasure. {@code
+ * BaseMessageEvent} allows the same method to accept both {@code TimedEvents<NetworkEvent>} and
+ * {@code TimedEvents<MessageEvent>}.
+ *
+ * <p>This class should only be extended by {@code NetworkEvent} and {@code MessageEvent}.
+ *
+ * @deprecated This class is for internal use only.
+ * @since 0.12
+ */
+@Deprecated
+public abstract class BaseMessageEvent {
+ // package protected to avoid users to extend it.
+ BaseMessageEvent() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/BlankSpan.java b/api/src/main/java/io/opencensus/trace/BlankSpan.java
new file mode 100644
index 00000000..af6456d3
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/BlankSpan.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import io.opencensus.internal.Utils;
+import java.util.Map;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * The {@code BlankSpan} is a singleton class, which is the default {@link Span} that is used when
+ * no {@code Span} implementation is available. All operations are no-op.
+ *
+ * <p>Used also to stop tracing, see {@link Tracer#withSpan}.
+ *
+ * @since 0.5
+ */
+@Immutable
+public final class BlankSpan extends Span {
+ /**
+ * Singleton instance of this class.
+ *
+ * @since 0.5
+ */
+ public static final BlankSpan INSTANCE = new BlankSpan();
+
+ private BlankSpan() {
+ super(SpanContext.INVALID, null);
+ }
+
+ /** No-op implementation of the {@link Span#putAttribute(String, AttributeValue)} method. */
+ @Override
+ public void putAttribute(String key, AttributeValue value) {
+ Utils.checkNotNull(key, "key");
+ Utils.checkNotNull(value, "value");
+ }
+
+ /** No-op implementation of the {@link Span#putAttributes(Map)} method. */
+ @Override
+ public void putAttributes(Map<String, AttributeValue> attributes) {
+ Utils.checkNotNull(attributes, "attributes");
+ }
+
+ /** No-op implementation of the {@link Span#addAnnotation(String, Map)} method. */
+ @Override
+ public void addAnnotation(String description, Map<String, AttributeValue> attributes) {
+ Utils.checkNotNull(description, "description");
+ Utils.checkNotNull(attributes, "attributes");
+ }
+
+ /** No-op implementation of the {@link Span#addAnnotation(Annotation)} method. */
+ @Override
+ public void addAnnotation(Annotation annotation) {
+ Utils.checkNotNull(annotation, "annotation");
+ }
+
+ /** No-op implementation of the {@link Span#addNetworkEvent(NetworkEvent)} method. */
+ @Override
+ @Deprecated
+ public void addNetworkEvent(NetworkEvent networkEvent) {}
+
+ /** No-op implementation of the {@link Span#addMessageEvent(MessageEvent)} method. */
+ @Override
+ public void addMessageEvent(MessageEvent messageEvent) {
+ Utils.checkNotNull(messageEvent, "messageEvent");
+ }
+
+ /** No-op implementation of the {@link Span#addLink(Link)} method. */
+ @Override
+ public void addLink(Link link) {
+ Utils.checkNotNull(link, "link");
+ }
+
+ @Override
+ public void setStatus(Status status) {
+ Utils.checkNotNull(status, "status");
+ }
+
+ /** No-op implementation of the {@link Span#end(EndSpanOptions)} method. */
+ @Override
+ public void end(EndSpanOptions options) {
+ Utils.checkNotNull(options, "options");
+ }
+
+ @Override
+ public String toString() {
+ return "BlankSpan";
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/CurrentSpanUtils.java b/api/src/main/java/io/opencensus/trace/CurrentSpanUtils.java
new file mode 100644
index 00000000..aa2f055a
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/CurrentSpanUtils.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import io.grpc.Context;
+import io.opencensus.common.Scope;
+import io.opencensus.trace.unsafe.ContextUtils;
+import java.util.concurrent.Callable;
+import javax.annotation.Nullable;
+
+/** Util methods/functionality to interact with the {@link Span} in the {@link io.grpc.Context}. */
+final class CurrentSpanUtils {
+ // No instance of this class.
+ private CurrentSpanUtils() {}
+
+ /**
+ * Returns The {@link Span} from the current context.
+ *
+ * @return The {@code Span} from the current context.
+ */
+ @Nullable
+ static Span getCurrentSpan() {
+ return ContextUtils.CONTEXT_SPAN_KEY.get();
+ }
+
+ /**
+ * Enters the scope of code where the given {@link Span} is in the current context, and returns an
+ * object that represents that scope. The scope is exited when the returned object is closed.
+ *
+ * <p>Supports try-with-resource idiom.
+ *
+ * @param span The {@code Span} to be set to the current context.
+ * @param endSpan if {@code true} the returned {@code Scope} will close the {@code Span}.
+ * @return An object that defines a scope where the given {@code Span} is set to the current
+ * context.
+ */
+ static Scope withSpan(Span span, boolean endSpan) {
+ return new ScopeInSpan(span, endSpan);
+ }
+
+ /**
+ * Wraps a {@link Runnable} so that it executes with the {@code span} as the current {@code Span}.
+ *
+ * @param span the {@code Span} to be set as current.
+ * @param endSpan if {@code true} the returned {@code Runnable} will close the {@code Span}.
+ * @param runnable the {@code Runnable} to run in the {@code Span}.
+ * @return the wrapped {@code Runnable}.
+ */
+ static Runnable withSpan(Span span, boolean endSpan, Runnable runnable) {
+ return new RunnableInSpan(span, runnable, endSpan);
+ }
+
+ /**
+ * Wraps a {@link Callable} so that it executes with the {@code span} as the current {@code Span}.
+ *
+ * @param span the {@code Span} to be set as current.
+ * @param endSpan if {@code true} the returned {@code Runnable} will close the {@code Span}.
+ * @param callable the {@code Callable} to run in the {@code Span}.
+ * @return the wrapped {@code Callable}.
+ */
+ static <C> Callable<C> withSpan(Span span, boolean endSpan, Callable<C> callable) {
+ return new CallableInSpan<C>(span, callable, endSpan);
+ }
+
+ // Defines an arbitrary scope of code as a traceable operation. Supports try-with-resources idiom.
+ private static final class ScopeInSpan implements Scope {
+ private final Context origContext;
+ private final Span span;
+ private final boolean endSpan;
+
+ /**
+ * Constructs a new {@link ScopeInSpan}.
+ *
+ * @param span is the {@code Span} to be added to the current {@code io.grpc.Context}.
+ */
+ private ScopeInSpan(Span span, boolean endSpan) {
+ this.span = span;
+ this.endSpan = endSpan;
+ origContext = Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach();
+ }
+
+ @Override
+ public void close() {
+ Context.current().detach(origContext);
+ if (endSpan) {
+ span.end();
+ }
+ }
+ }
+
+ private static final class RunnableInSpan implements Runnable {
+ // TODO(bdrutu): Investigate if the extra private visibility increases the generated bytecode.
+ private final Span span;
+ private final Runnable runnable;
+ private final boolean endSpan;
+
+ private RunnableInSpan(Span span, Runnable runnable, boolean endSpan) {
+ this.span = span;
+ this.runnable = runnable;
+ this.endSpan = endSpan;
+ }
+
+ @Override
+ public void run() {
+ Context origContext =
+ Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach();
+ try {
+ runnable.run();
+ } catch (Throwable t) {
+ setErrorStatus(span, t);
+ if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ } else if (t instanceof Error) {
+ throw (Error) t;
+ }
+ throw new RuntimeException("unexpected", t);
+ } finally {
+ Context.current().detach(origContext);
+ if (endSpan) {
+ span.end();
+ }
+ }
+ }
+ }
+
+ private static final class CallableInSpan<V> implements Callable<V> {
+ private final Span span;
+ private final Callable<V> callable;
+ private final boolean endSpan;
+
+ private CallableInSpan(Span span, Callable<V> callable, boolean endSpan) {
+ this.span = span;
+ this.callable = callable;
+ this.endSpan = endSpan;
+ }
+
+ @Override
+ public V call() throws Exception {
+ Context origContext =
+ Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach();
+ try {
+ return callable.call();
+ } catch (Exception e) {
+ setErrorStatus(span, e);
+ throw e;
+ } catch (Throwable t) {
+ setErrorStatus(span, t);
+ if (t instanceof Error) {
+ throw (Error) t;
+ }
+ throw new RuntimeException("unexpected", t);
+ } finally {
+ Context.current().detach(origContext);
+ if (endSpan) {
+ span.end();
+ }
+ }
+ }
+ }
+
+ private static void setErrorStatus(Span span, Throwable t) {
+ span.setStatus(
+ Status.UNKNOWN.withDescription(
+ t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage()));
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/EndSpanOptions.java b/api/src/main/java/io/opencensus/trace/EndSpanOptions.java
new file mode 100644
index 00000000..b0d9a470
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/EndSpanOptions.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import java.util.Collection;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that enables overriding the default values used when ending a {@link Span}. Allows
+ * overriding the {@link Status status}.
+ *
+ * @since 0.5
+ */
+@Immutable
+@AutoValue
+public abstract class EndSpanOptions {
+ /**
+ * The default {@code EndSpanOptions}.
+ *
+ * @since 0.5
+ */
+ public static final EndSpanOptions DEFAULT = builder().build();
+
+ /**
+ * Returns a new {@link Builder} with default options.
+ *
+ * @return a new {@code Builder} with default options.
+ * @since 0.5
+ */
+ public static Builder builder() {
+ return new AutoValue_EndSpanOptions.Builder().setSampleToLocalSpanStore(false);
+ }
+
+ /**
+ * If {@code true} this is equivalent with calling the {@link
+ * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} in
+ * advance for this span name.
+ *
+ * <p>It is strongly recommended to use the {@link
+ * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} API
+ * instead.
+ *
+ * @return {@code true} if the name of the {@code Span} should be registered to the {@code
+ * io.opencensus.trace.export.SampledSpanStore}.
+ * @since 0.8
+ */
+ @ExperimentalApi
+ public abstract boolean getSampleToLocalSpanStore();
+
+ /**
+ * Returns the status.
+ *
+ * <p>If {@code null} then the {@link Span} will record the {@link Status} set via {@link
+ * Span#setStatus(Status)} or the default {@link Status#OK} if no status was set.
+ *
+ * @return the status.
+ * @since 0.5
+ */
+ @Nullable
+ public abstract Status getStatus();
+
+ /**
+ * Builder class for {@link EndSpanOptions}.
+ *
+ * @since 0.5
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the status for the {@link Span}.
+ *
+ * <p>If set, this will override the status set via {@link Span#setStatus(Status)}.
+ *
+ * @param status the status.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract Builder setStatus(Status status);
+
+ /**
+ * If set to {@code true} this is equivalent with calling the {@link
+ * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} in
+ * advance for the given span name.
+ *
+ * <p>WARNING: setting this option to a randomly generated span name can OOM your process
+ * because the library will save samples for each name.
+ *
+ * <p>It is strongly recommended to use the {@link
+ * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} API
+ * instead.
+ *
+ * @return this.
+ * @since 0.8
+ */
+ @ExperimentalApi
+ public abstract Builder setSampleToLocalSpanStore(boolean sampleToLocalSpanStore);
+
+ /**
+ * Builds and returns a {@code EndSpanOptions} with the desired settings.
+ *
+ * @return a {@code EndSpanOptions} with the desired settings.
+ * @since 0.5
+ */
+ public abstract EndSpanOptions build();
+
+ Builder() {}
+ }
+
+ EndSpanOptions() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/Link.java b/api/src/main/java/io/opencensus/trace/Link.java
new file mode 100644
index 00000000..1de79710
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Link.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import com.google.auto.value.AutoValue;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A link to a {@link Span} from a different trace.
+ *
+ * <p>It requires a {@link Type} which describes the relationship with the linked {@code Span} and
+ * the identifiers of the linked {@code Span}.
+ *
+ * <p>Used (for example) in batching operations, where a single batch handler processes multiple
+ * requests from different traces.
+ *
+ * @since 0.5
+ */
+@Immutable
+@AutoValue
+public abstract class Link {
+ private static final Map<String, AttributeValue> EMPTY_ATTRIBUTES = Collections.emptyMap();
+
+ /**
+ * The relationship with the linked {@code Span} relative to the current {@code Span}.
+ *
+ * @since 0.5
+ */
+ public enum Type {
+ /**
+ * When the linked {@code Span} is a child of the current {@code Span}.
+ *
+ * @since 0.5
+ */
+ CHILD_LINKED_SPAN,
+ /**
+ * When the linked {@code Span} is a parent of the current {@code Span}.
+ *
+ * @since 0.5
+ */
+ PARENT_LINKED_SPAN
+ }
+
+ /**
+ * Returns a new {@code Link}.
+ *
+ * @param context the context of the linked {@code Span}.
+ * @param type the type of the relationship with the linked {@code Span}.
+ * @return a new {@code Link}.
+ * @since 0.5
+ */
+ public static Link fromSpanContext(SpanContext context, Type type) {
+ return new AutoValue_Link(context.getTraceId(), context.getSpanId(), type, EMPTY_ATTRIBUTES);
+ }
+
+ /**
+ * Returns a new {@code Link}.
+ *
+ * @param context the context of the linked {@code Span}.
+ * @param type the type of the relationship with the linked {@code Span}.
+ * @param attributes the attributes of the {@code Link}.
+ * @return a new {@code Link}.
+ * @since 0.5
+ */
+ public static Link fromSpanContext(
+ SpanContext context, Type type, Map<String, AttributeValue> attributes) {
+ return new AutoValue_Link(
+ context.getTraceId(),
+ context.getSpanId(),
+ type,
+ Collections.unmodifiableMap(new HashMap<String, AttributeValue>(attributes)));
+ }
+
+ /**
+ * Returns the {@code TraceId}.
+ *
+ * @return the {@code TraceId}.
+ * @since 0.5
+ */
+ public abstract TraceId getTraceId();
+
+ /**
+ * Returns the {@code SpanId}.
+ *
+ * @return the {@code SpanId}
+ * @since 0.5
+ */
+ public abstract SpanId getSpanId();
+
+ /**
+ * Returns the {@code Type}.
+ *
+ * @return the {@code Type}.
+ * @since 0.5
+ */
+ public abstract Type getType();
+
+ /**
+ * Returns the set of attributes.
+ *
+ * @return the set of attributes.
+ * @since 0.5
+ */
+ public abstract Map<String, AttributeValue> getAttributes();
+
+ Link() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/LowerCaseBase16Encoding.java b/api/src/main/java/io/opencensus/trace/LowerCaseBase16Encoding.java
new file mode 100644
index 00000000..bca95868
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/LowerCaseBase16Encoding.java
@@ -0,0 +1,91 @@
+/*
+ * 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.trace;
+
+import io.opencensus.internal.Utils;
+import java.util.Arrays;
+
+/** Internal copy of the Guava implementation of the {@code BaseEncoding.base16().lowerCase()}. */
+final class LowerCaseBase16Encoding {
+ private static final String ALPHABET = "0123456789abcdef";
+ private static final int ASCII_CHARACTERS = 128;
+ private static final char[] ENCODING = buildEncodingArray();
+ private static final byte[] DECODING = buildDecodingArray();
+
+ private static char[] buildEncodingArray() {
+ char[] encoding = new char[512];
+ for (int i = 0; i < 256; ++i) {
+ encoding[i] = ALPHABET.charAt(i >>> 4);
+ encoding[i | 0x100] = ALPHABET.charAt(i & 0xF);
+ }
+ return encoding;
+ }
+
+ private static byte[] buildDecodingArray() {
+ byte[] decoding = new byte[ASCII_CHARACTERS];
+ Arrays.fill(decoding, (byte) -1);
+ for (int i = 0; i < ALPHABET.length(); i++) {
+ char c = ALPHABET.charAt(i);
+ decoding[c] = (byte) i;
+ }
+ return decoding;
+ }
+
+ /**
+ * Encodes the specified byte array, and returns the encoded {@code String}.
+ *
+ * @param bytes byte array to be encoded.
+ * @return the encoded {@code String}.
+ */
+ static String encodeToString(byte[] bytes) {
+ StringBuilder stringBuilder = new StringBuilder(bytes.length * 2);
+ for (byte byteVal : bytes) {
+ int b = byteVal & 0xFF;
+ stringBuilder.append(ENCODING[b]);
+ stringBuilder.append(ENCODING[b | 0x100]);
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * Decodes the specified character sequence, and returns the resulting {@code byte[]}.
+ *
+ * @param chars the character sequence to be decoded.
+ * @return the resulting {@code byte[]}
+ * @throws IllegalArgumentException if the input is not a valid encoded string according to this
+ * encoding.
+ */
+ static byte[] decodeToBytes(CharSequence chars) {
+ Utils.checkArgument(chars.length() % 2 == 0, "Invalid input length " + chars.length());
+ int bytesWritten = 0;
+ byte[] bytes = new byte[chars.length() / 2];
+ for (int i = 0; i < chars.length(); i += 2) {
+ bytes[bytesWritten++] = decodeByte(chars.charAt(i), chars.charAt(i + 1));
+ }
+ return bytes;
+ }
+
+ private static byte decodeByte(char hi, char lo) {
+ Utils.checkArgument(lo < ASCII_CHARACTERS && DECODING[lo] != -1, "Invalid character " + lo);
+ Utils.checkArgument(hi < ASCII_CHARACTERS && DECODING[hi] != -1, "Invalid character " + hi);
+ int decoded = DECODING[hi] << 4 | DECODING[lo];
+ return (byte) decoded;
+ }
+
+ // Private constructor to disallow instances.
+ private LowerCaseBase16Encoding() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/MessageEvent.java b/api/src/main/java/io/opencensus/trace/MessageEvent.java
new file mode 100644
index 00000000..4b693aaa
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/MessageEvent.java
@@ -0,0 +1,151 @@
+/*
+ * 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.trace;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.Utils;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that represents a generic messaging event. This class can represent messaging happened in
+ * any layer, especially higher application layer. Thus, it can be used when recording events in
+ * pipeline works, in-process bidirectional streams and batch processing.
+ *
+ * <p>It requires a {@link Type type} and a message id that serves to uniquely identify each
+ * message. It can optionally have information about the message size.
+ *
+ * @since 0.12
+ */
+@Immutable
+@AutoValue
+@SuppressWarnings("deprecation")
+public abstract class MessageEvent extends BaseMessageEvent {
+ /**
+ * Available types for a {@code MessageEvent}.
+ *
+ * @since 0.12
+ */
+ public enum Type {
+ /**
+ * When the message was sent.
+ *
+ * @since 0.12
+ */
+ SENT,
+ /**
+ * When the message was received.
+ *
+ * @since 0.12
+ */
+ RECEIVED,
+ }
+
+ /**
+ * Returns a new {@link Builder} with default values.
+ *
+ * @param type designates whether this is a send or receive message.
+ * @param messageId serves to uniquely identify each message.
+ * @return a new {@code Builder} with default values.
+ * @throws NullPointerException if {@code type} is {@code null}.
+ * @since 0.12
+ */
+ public static Builder builder(Type type, long messageId) {
+ return new AutoValue_MessageEvent.Builder()
+ .setType(Utils.checkNotNull(type, "type"))
+ .setMessageId(messageId)
+ // We need to set a value for the message size because the autovalue requires all
+ // primitives to be initialized.
+ .setUncompressedMessageSize(0)
+ .setCompressedMessageSize(0);
+ }
+
+ /**
+ * Returns the type of the {@code MessageEvent}.
+ *
+ * @return the type of the {@code MessageEvent}.
+ * @since 0.12
+ */
+ public abstract Type getType();
+
+ /**
+ * Returns the message id argument that serves to uniquely identify each message.
+ *
+ * @return the message id of the {@code MessageEvent}.
+ * @since 0.12
+ */
+ public abstract long getMessageId();
+
+ /**
+ * Returns the uncompressed size in bytes of the {@code MessageEvent}.
+ *
+ * @return the uncompressed size in bytes of the {@code MessageEvent}.
+ * @since 0.12
+ */
+ public abstract long getUncompressedMessageSize();
+
+ /**
+ * Returns the compressed size in bytes of the {@code MessageEvent}.
+ *
+ * @return the compressed size in bytes of the {@code MessageEvent}.
+ * @since 0.12
+ */
+ public abstract long getCompressedMessageSize();
+
+ /**
+ * Builder class for {@link MessageEvent}.
+ *
+ * @since 0.12
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ // Package protected methods because these values are mandatory and set only in the
+ // MessageEvent#builder() function.
+ abstract Builder setType(Type type);
+
+ abstract Builder setMessageId(long messageId);
+
+ /**
+ * Sets the uncompressed message size.
+ *
+ * @param uncompressedMessageSize represents the uncompressed size in bytes of this message.
+ * @return this.
+ * @since 0.12
+ */
+ public abstract Builder setUncompressedMessageSize(long uncompressedMessageSize);
+
+ /**
+ * Sets the compressed message size.
+ *
+ * @param compressedMessageSize represents the compressed size in bytes of this message.
+ * @return this.
+ * @since 0.12
+ */
+ public abstract Builder setCompressedMessageSize(long compressedMessageSize);
+
+ /**
+ * Builds and returns a {@code MessageEvent} with the desired values.
+ *
+ * @return a {@code MessageEvent} with the desired values.
+ * @since 0.12
+ */
+ public abstract MessageEvent build();
+
+ Builder() {}
+ }
+
+ MessageEvent() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/NetworkEvent.java b/api/src/main/java/io/opencensus/trace/NetworkEvent.java
new file mode 100644
index 00000000..722029e5
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/NetworkEvent.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Timestamp;
+import io.opencensus.internal.Utils;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that represents a network event. It requires a {@link Type type} and a message id that
+ * serves to uniquely identify each network message. It can optionally can have information about
+ * the kernel time and message size.
+ *
+ * @deprecated Use {@link MessageEvent}.
+ * @since 0.5
+ */
+@Immutable
+@AutoValue
+@AutoValue.CopyAnnotations
+@Deprecated
+public abstract class NetworkEvent extends io.opencensus.trace.BaseMessageEvent {
+ /**
+ * Available types for a {@code NetworkEvent}.
+ *
+ * @since 0.5
+ */
+ public enum Type {
+ /**
+ * When the message was sent.
+ *
+ * @since 0.5
+ */
+ SENT,
+ /**
+ * When the message was received.
+ *
+ * @since 0.5
+ */
+ RECV,
+ }
+
+ /**
+ * Returns a new {@link Builder} with default values.
+ *
+ * @param type designates whether this is a network send or receive message.
+ * @param messageId serves to uniquely identify each network message.
+ * @return a new {@code Builder} with default values.
+ * @throws NullPointerException if {@code type} is {@code null}.
+ * @since 0.5
+ */
+ public static Builder builder(Type type, long messageId) {
+ return new AutoValue_NetworkEvent.Builder()
+ .setType(Utils.checkNotNull(type, "type"))
+ .setMessageId(messageId)
+ // We need to set a value for the message size because the autovalue requires all
+ // primitives to be initialized.
+ .setUncompressedMessageSize(0)
+ .setCompressedMessageSize(0);
+ }
+
+ /**
+ * Returns the kernel timestamp associated with the {@code NetworkEvent} or {@code null} if not
+ * set.
+ *
+ * @return the kernel timestamp associated with the {@code NetworkEvent} or {@code null} if not
+ * set.
+ * @since 0.5
+ */
+ @Nullable
+ public abstract Timestamp getKernelTimestamp();
+
+ /**
+ * Returns the type of the {@code NetworkEvent}.
+ *
+ * @return the type of the {@code NetworkEvent}.
+ * @since 0.5
+ */
+ public abstract Type getType();
+
+ /**
+ * Returns the message id argument that serves to uniquely identify each network message.
+ *
+ * @return the message id of the {@code NetworkEvent}.
+ * @since 0.5
+ */
+ public abstract long getMessageId();
+
+ /**
+ * Returns the uncompressed size in bytes of the {@code NetworkEvent}.
+ *
+ * @return the uncompressed size in bytes of the {@code NetworkEvent}.
+ * @since 0.6
+ */
+ public abstract long getUncompressedMessageSize();
+
+ /**
+ * Returns the compressed size in bytes of the {@code NetworkEvent}.
+ *
+ * @return the compressed size in bytes of the {@code NetworkEvent}.
+ * @since 0.6
+ */
+ public abstract long getCompressedMessageSize();
+
+ /**
+ * Returns the uncompressed size in bytes of the {@code NetworkEvent}.
+ *
+ * @deprecated Use {@link #getUncompressedMessageSize}.
+ * @return the uncompressed size in bytes of the {@code NetworkEvent}.
+ * @since 0.5
+ */
+ @Deprecated
+ public long getMessageSize() {
+ return getUncompressedMessageSize();
+ }
+
+ /**
+ * Builder class for {@link NetworkEvent}.
+ *
+ * @deprecated {@link NetworkEvent} is deprecated. Please use {@link MessageEvent} and its builder
+ * {@link MessageEvent.Builder}.
+ * @since 0.5
+ */
+ @AutoValue.Builder
+ @Deprecated
+ public abstract static class Builder {
+ // Package protected methods because these values are mandatory and set only in the
+ // NetworkEvent#builder() function.
+ abstract Builder setType(Type type);
+
+ abstract Builder setMessageId(long messageId);
+
+ /**
+ * Sets the kernel timestamp.
+ *
+ * @param kernelTimestamp The kernel timestamp of the event.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract Builder setKernelTimestamp(@Nullable Timestamp kernelTimestamp);
+
+ /**
+ * Sets the uncompressed message size.
+ *
+ * @deprecated Use {@link #setUncompressedMessageSize}.
+ * @param messageSize represents the uncompressed size in bytes of this message.
+ * @return this.
+ * @since 0.5
+ */
+ @Deprecated
+ public Builder setMessageSize(long messageSize) {
+ return setUncompressedMessageSize(messageSize);
+ }
+
+ /**
+ * Sets the uncompressed message size.
+ *
+ * @param uncompressedMessageSize represents the uncompressed size in bytes of this message.
+ * @return this.
+ * @since 0.6
+ */
+ public abstract Builder setUncompressedMessageSize(long uncompressedMessageSize);
+
+ /**
+ * Sets the compressed message size.
+ *
+ * @param compressedMessageSize represents the compressed size in bytes of this message.
+ * @return this.
+ * @since 0.6
+ */
+ public abstract Builder setCompressedMessageSize(long compressedMessageSize);
+
+ /**
+ * Builds and returns a {@code NetworkEvent} with the desired values.
+ *
+ * @return a {@code NetworkEvent} with the desired values.
+ * @since 0.5
+ */
+ public abstract NetworkEvent build();
+
+ Builder() {}
+ }
+
+ NetworkEvent() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/Sampler.java b/api/src/main/java/io/opencensus/trace/Sampler.java
new file mode 100644
index 00000000..e89af89b
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Sampler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * Sampler is used to make decisions on {@link Span} sampling.
+ *
+ * @since 0.5
+ */
+public abstract class Sampler {
+ /**
+ * Called during {@link Span} creation to make a sampling decision.
+ *
+ * @param parentContext the parent span's {@link SpanContext}. {@code null} if this is a root
+ * span.
+ * @param hasRemoteParent {@code true} if the parent {@code Span} is remote. {@code null} if this
+ * is a root span.
+ * @param traceId the {@link TraceId} for the new {@code Span}. This will be identical to that in
+ * the parentContext, unless this is a root span.
+ * @param spanId the {@link SpanId} for the new {@code Span}.
+ * @param name the name of the new {@code Span}.
+ * @param parentLinks the parentLinks associated with the new {@code Span}.
+ * @return {@code true} if the {@code Span} is sampled.
+ * @since 0.5
+ */
+ public abstract boolean shouldSample(
+ @Nullable SpanContext parentContext,
+ @Nullable Boolean hasRemoteParent,
+ TraceId traceId,
+ SpanId spanId,
+ String name,
+ List<Span> parentLinks);
+
+ /**
+ * Returns the description of this {@code Sampler}. This may be displayed on debug pages or in the
+ * logs.
+ *
+ * <p>Example: "ProbabilitySampler{0.000100}"
+ *
+ * @return the description of this {@code Sampler}.
+ * @since 0.6
+ */
+ public abstract String getDescription();
+}
diff --git a/api/src/main/java/io/opencensus/trace/Span.java b/api/src/main/java/io/opencensus/trace/Span.java
new file mode 100644
index 00000000..8f8253b4
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Span.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.internal.BaseMessageEventUtils;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/**
+ * An abstract class that represents a span. It has an associated {@link SpanContext} and a set of
+ * {@link Options}.
+ *
+ * <p>Spans are created by the {@link SpanBuilder#startSpan} method.
+ *
+ * <p>{@code Span} <b>must</b> be ended by calling {@link #end()} or {@link #end(EndSpanOptions)}
+ *
+ * @since 0.5
+ */
+public abstract class Span {
+ private static final Map<String, AttributeValue> EMPTY_ATTRIBUTES = Collections.emptyMap();
+
+ // Contains the identifiers associated with this Span.
+ private final SpanContext context;
+
+ // Contains the options associated with this Span. This object is immutable.
+ private final Set<Options> options;
+
+ /**
+ * {@code Span} options. These options are NOT propagated to child spans. These options determine
+ * features such as whether a {@code Span} should record any annotations or events.
+ *
+ * @since 0.5
+ */
+ public enum Options {
+ /**
+ * This option is set if the Span is part of a sampled distributed trace OR {@link
+ * SpanBuilder#setRecordEvents(boolean)} was called with true.
+ *
+ * @since 0.5
+ */
+ RECORD_EVENTS;
+ }
+
+ private static final Set<Options> DEFAULT_OPTIONS =
+ Collections.unmodifiableSet(EnumSet.noneOf(Options.class));
+
+ /**
+ * Creates a new {@code Span}.
+ *
+ * @param context the context associated with this {@code Span}.
+ * @param options the options associated with this {@code Span}. If {@code null} then default
+ * options will be set.
+ * @throws NullPointerException if context is {@code null}.
+ * @throws IllegalArgumentException if the {@code SpanContext} is sampled but no RECORD_EVENTS
+ * options.
+ * @since 0.5
+ */
+ protected Span(SpanContext context, @Nullable EnumSet<Options> options) {
+ this.context = Utils.checkNotNull(context, "context");
+ this.options =
+ options == null
+ ? DEFAULT_OPTIONS
+ : Collections.<Options>unmodifiableSet(EnumSet.copyOf(options));
+ Utils.checkArgument(
+ !context.getTraceOptions().isSampled() || (this.options.contains(Options.RECORD_EVENTS)),
+ "Span is sampled, but does not have RECORD_EVENTS set.");
+ }
+
+ /**
+ * Sets an attribute to the {@code Span}. If the {@code Span} previously contained a mapping for
+ * the key, the old value is replaced by the specified value.
+ *
+ * @param key the key for this attribute.
+ * @param value the value for this attribute.
+ * @since 0.6
+ */
+ public void putAttribute(String key, AttributeValue value) {
+ // Not final because for performance reasons we want to override this in the implementation.
+ // Also a default implementation is needed to not break the compatibility (users may extend this
+ // for testing).
+ Utils.checkNotNull(key, "key");
+ Utils.checkNotNull(value, "value");
+ putAttributes(Collections.singletonMap(key, value));
+ }
+
+ /**
+ * Sets a set of attributes to the {@code Span}. The effect of this call is equivalent to that of
+ * calling {@link #putAttribute(String, AttributeValue)} once for each element in the specified
+ * map.
+ *
+ * @param attributes the attributes that will be added and associated with the {@code Span}.
+ * @since 0.6
+ */
+ public void putAttributes(Map<String, AttributeValue> attributes) {
+ // Not final because we want to start overriding this method from the beginning, this will
+ // allow us to remove the addAttributes faster. All implementations MUST override this method.
+ Utils.checkNotNull(attributes, "attributes");
+ addAttributes(attributes);
+ }
+
+ /**
+ * Sets a set of attributes to the {@code Span}. The effect of this call is equivalent to that of
+ * calling {@link #putAttribute(String, AttributeValue)} once for each element in the specified
+ * map.
+ *
+ * @deprecated Use {@link #putAttributes(Map)}
+ * @param attributes the attributes that will be added and associated with the {@code Span}.
+ * @since 0.5
+ */
+ @Deprecated
+ public void addAttributes(Map<String, AttributeValue> attributes) {
+ putAttributes(attributes);
+ }
+
+ /**
+ * Adds an annotation to the {@code Span}.
+ *
+ * @param description the description of the annotation time event.
+ * @since 0.5
+ */
+ public final void addAnnotation(String description) {
+ Utils.checkNotNull(description, "description");
+ addAnnotation(description, EMPTY_ATTRIBUTES);
+ }
+
+ /**
+ * Adds an annotation to the {@code Span}.
+ *
+ * @param description the description of the annotation time event.
+ * @param attributes the attributes that will be added; these are associated with this annotation,
+ * not the {@code Span} as for {@link #putAttributes(Map)}.
+ * @since 0.5
+ */
+ public abstract void addAnnotation(String description, Map<String, AttributeValue> attributes);
+
+ /**
+ * Adds an annotation to the {@code Span}.
+ *
+ * @param annotation the annotations to add.
+ * @since 0.5
+ */
+ public abstract void addAnnotation(Annotation annotation);
+
+ /**
+ * Adds a NetworkEvent to the {@code Span}.
+ *
+ * <p>This function is only intended to be used by RPC systems (either client or server), not by
+ * higher level applications.
+ *
+ * @param networkEvent the network event to add.
+ * @deprecated Use {@link #addMessageEvent}.
+ * @since 0.5
+ */
+ @Deprecated
+ public void addNetworkEvent(NetworkEvent networkEvent) {
+ addMessageEvent(BaseMessageEventUtils.asMessageEvent(networkEvent));
+ }
+
+ /**
+ * Adds a MessageEvent to the {@code Span}.
+ *
+ * <p>This function can be used by higher level applications to record messaging event.
+ *
+ * <p>This method should always be overridden by users whose API versions are larger or equal to
+ * {@code 0.12}.
+ *
+ * @param messageEvent the message to add.
+ * @since 0.12
+ */
+ public void addMessageEvent(MessageEvent messageEvent) {
+ // Default implementation by invoking addNetworkEvent() so that any existing derived classes,
+ // including implementation and the mocked ones, do not need to override this method explicitly.
+ Utils.checkNotNull(messageEvent, "messageEvent");
+ addNetworkEvent(BaseMessageEventUtils.asNetworkEvent(messageEvent));
+ }
+
+ /**
+ * Adds a {@link Link} to the {@code Span}.
+ *
+ * <p>Used (for example) in batching operations, where a single batch handler processes multiple
+ * requests from different traces.
+ *
+ * @param link the link to add.
+ * @since 0.5
+ */
+ public abstract void addLink(Link link);
+
+ /**
+ * Sets the {@link Status} to the {@code Span}.
+ *
+ * <p>If used, this will override the default {@code Span} status. Default is {@link Status#OK}.
+ *
+ * <p>Only the value of the last call will be recorded, and implementations are free to ignore
+ * previous calls. If the status is set via {@link EndSpanOptions.Builder#setStatus(Status)} that
+ * will always be the last call.
+ *
+ * @param status the {@link Status} to set.
+ * @since 0.9
+ */
+ public void setStatus(Status status) {
+ // Implemented as no-op for backwards compatibility (for example gRPC extends Span in tests).
+ // Implementation must override this method.
+ Utils.checkNotNull(status, "status");
+ }
+
+ /**
+ * Marks the end of {@code Span} execution with the given options.
+ *
+ * <p>Only the timing of the first end call for a given {@code Span} will be recorded, and
+ * implementations are free to ignore all further calls.
+ *
+ * @param options the options to be used for the end of the {@code Span}.
+ * @since 0.5
+ */
+ public abstract void end(EndSpanOptions options);
+
+ /**
+ * Marks the end of {@code Span} execution with the default options.
+ *
+ * <p>Only the timing of the first end call for a given {@code Span} will be recorded, and
+ * implementations are free to ignore all further calls.
+ *
+ * @since 0.5
+ */
+ public final void end() {
+ end(EndSpanOptions.DEFAULT);
+ }
+
+ /**
+ * Returns the {@code SpanContext} associated with this {@code Span}.
+ *
+ * @return the {@code SpanContext} associated with this {@code Span}.
+ * @since 0.5
+ */
+ public final SpanContext getContext() {
+ return context;
+ }
+
+ /**
+ * Returns the options associated with this {@code Span}.
+ *
+ * @return the options associated with this {@code Span}.
+ * @since 0.5
+ */
+ public final Set<Options> getOptions() {
+ return options;
+ }
+
+ /**
+ * Type of span. Can be used to specify additional relationships between spans in addition to a
+ * parent/child relationship.
+ *
+ * @since 0.14
+ */
+ public enum Kind {
+ /**
+ * Indicates that the span covers server-side handling of an RPC or other remote request.
+ *
+ * @since 0.14
+ */
+ SERVER,
+
+ /**
+ * Indicates that the span covers the client-side wrapper around an RPC or other remote request.
+ *
+ * @since 0.14
+ */
+ CLIENT
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/SpanBuilder.java b/api/src/main/java/io/opencensus/trace/SpanBuilder.java
new file mode 100644
index 00000000..f3a436a6
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/SpanBuilder.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import com.google.errorprone.annotations.MustBeClosed;
+import io.opencensus.common.Scope;
+import io.opencensus.internal.Utils;
+import java.util.List;
+import java.util.concurrent.Callable;
+import javax.annotation.Nullable;
+
+/**
+ * {@link SpanBuilder} is used to construct {@link Span} instances which define arbitrary scopes of
+ * code that are sampled for distributed tracing as a single atomic unit.
+ *
+ * <p>This is a simple example where all the work is being done within a single scope and a single
+ * thread and the Context is automatically propagated:
+ *
+ * <pre>{@code
+ * class MyClass {
+ * private static final Tracer tracer = Tracing.getTracer();
+ * void doWork {
+ * // Create a Span as a child of the current Span.
+ * try (Scope ss = tracer.spanBuilder("MyChildSpan").startScopedSpan()) {
+ * tracer.getCurrentSpan().addAnnotation("my annotation");
+ * doSomeWork(); // Here the new span is in the current Context, so it can be used
+ * // implicitly anywhere down the stack.
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>There might be cases where you do not perform all the work inside one static scope and the
+ * Context is automatically propagated:
+ *
+ * <pre>{@code
+ * class MyRpcServerInterceptorListener implements RpcServerInterceptor.Listener {
+ * private static final Tracer tracer = Tracing.getTracer();
+ * private Span mySpan;
+ *
+ * public MyRpcInterceptor() {}
+ *
+ * public void onRequest(String rpcName, Metadata metadata) {
+ * // Create a Span as a child of the remote Span.
+ * mySpan = tracer.spanBuilderWithRemoteParent(
+ * getTraceContextFromMetadata(metadata), rpcName).startSpan();
+ * }
+ *
+ * public void onExecuteHandler(ServerCallHandler serverCallHandler) {
+ * try (Scope ws = tracer.withSpan(mySpan)) {
+ * tracer.getCurrentSpan().addAnnotation("Start rpc execution.");
+ * serverCallHandler.run(); // Here the new span is in the current Context, so it can be
+ * // used implicitly anywhere down the stack.
+ * }
+ * }
+ *
+ * // Called when the RPC is canceled and guaranteed onComplete will not be called.
+ * public void onCancel() {
+ * // IMPORTANT: DO NOT forget to ended the Span here as the work is done.
+ * mySpan.end(EndSpanOptions.builder().setStatus(Status.CANCELLED));
+ * }
+ *
+ * // Called when the RPC is done and guaranteed onCancel will not be called.
+ * public void onComplete(RpcStatus rpcStatus) {
+ * // IMPORTANT: DO NOT forget to ended the Span here as the work is done.
+ * mySpan.end(EndSpanOptions.builder().setStatus(rpcStatusToCanonicalTraceStatus(status));
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>This is a simple example where all the work is being done within a single scope and the
+ * Context is manually propagated:
+ *
+ * <pre>{@code
+ * class MyClass {
+ * private static final Tracer tracer = Tracing.getTracer();
+ * void DoWork(Span parent) {
+ * Span childSpan = tracer.spanBuilderWithExplicitParent("MyChildSpan", parent).startSpan();
+ * childSpan.addAnnotation("my annotation");
+ * try {
+ * doSomeWork(childSpan); // Manually propagate the new span down the stack.
+ * } finally {
+ * // To make sure we end the span even in case of an exception.
+ * childSpan.end(); // Manually end the span.
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>If your Java version is less than Java SE 7, see {@link SpanBuilder#startSpan} and {@link
+ * SpanBuilder#startScopedSpan} for usage examples.
+ *
+ * @since 0.5
+ */
+public abstract class SpanBuilder {
+
+ /**
+ * Sets the {@link Sampler} to use. If not set, the implementation will provide a default.
+ *
+ * @param sampler the {@code Sampler} to use when determining sampling for a {@code Span}.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract SpanBuilder setSampler(Sampler sampler);
+
+ /**
+ * Sets the {@code List} of parent links. Links are used to link {@link Span}s in different
+ * traces. Used (for example) in batching operations, where a single batch handler processes
+ * multiple requests from different traces.
+ *
+ * @param parentLinks new links to be added.
+ * @return this.
+ * @throws NullPointerException if {@code parentLinks} is {@code null}.
+ * @since 0.5
+ */
+ public abstract SpanBuilder setParentLinks(List<Span> parentLinks);
+
+ /**
+ * Sets the option {@link Span.Options#RECORD_EVENTS} for the newly created {@code Span}. If not
+ * called, the implementation will provide a default.
+ *
+ * @param recordEvents new value determining if this {@code Span} should have events recorded.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract SpanBuilder setRecordEvents(boolean recordEvents);
+
+ /**
+ * Sets the {@link Span.Kind} for the newly created {@code Span}. If not called, the
+ * implementation will provide a default.
+ *
+ * @param spanKind the kind of the newly created {@code Span}.
+ * @return this.
+ * @since 0.14
+ */
+ public SpanBuilder setSpanKind(@Nullable Span.Kind spanKind) {
+ return this;
+ }
+
+ /**
+ * Starts a new {@link Span}.
+ *
+ * <p>Users <b>must</b> manually call {@link Span#end()} or {@link Span#end(EndSpanOptions)} to
+ * end this {@code Span}.
+ *
+ * <p>Does not install the newly created {@code Span} to the current Context.
+ *
+ * <p>Example of usage:
+ *
+ * <pre>{@code
+ * class MyClass {
+ * private static final Tracer tracer = Tracing.getTracer();
+ * void DoWork(Span parent) {
+ * Span childSpan = tracer.spanBuilderWithExplicitParent("MyChildSpan", parent).startSpan();
+ * childSpan.addAnnotation("my annotation");
+ * try {
+ * doSomeWork(childSpan); // Manually propagate the new span down the stack.
+ * } finally {
+ * // To make sure we end the span even in case of an exception.
+ * childSpan.end(); // Manually end the span.
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * @return the newly created {@code Span}.
+ * @since 0.5
+ */
+ public abstract Span startSpan();
+
+ /**
+ * Starts a new span and sets it as the {@link Tracer#getCurrentSpan current span}.
+ *
+ * <p>Enters the scope of code where the newly created {@code Span} is in the current Context, and
+ * returns an object that represents that scope. When the returned object is closed, the scope is
+ * exited, the previous Context is restored, and the newly created {@code Span} is ended using
+ * {@link Span#end}.
+ *
+ * <p>Supports try-with-resource idiom.
+ *
+ * <p>Example of usage:
+ *
+ * <pre>{@code
+ * class MyClass {
+ * private static final Tracer tracer = Tracing.getTracer();
+ * void doWork {
+ * // Create a Span as a child of the current Span.
+ * try (Scope ss = tracer.spanBuilder("MyChildSpan").startScopedSpan()) {
+ * tracer.getCurrentSpan().addAnnotation("my annotation");
+ * doSomeWork(); // Here the new span is in the current Context, so it can be used
+ * // implicitly anywhere down the stack. Anytime in this closure the span
+ * // can be accessed via tracer.getCurrentSpan().
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>Prior to Java SE 7, you can use a finally block to ensure that a resource is closed (the
+ * {@code Span} is ended and removed from the Context) regardless of whether the try statement
+ * completes normally or abruptly.
+ *
+ * <p>Example of usage prior to Java SE7:
+ *
+ * <pre>{@code
+ * class MyClass {
+ * private static Tracer tracer = Tracing.getTracer();
+ * void doWork {
+ * // Create a Span as a child of the current Span.
+ * Scope ss = tracer.spanBuilder("MyChildSpan").startScopedSpan();
+ * try {
+ * tracer.getCurrentSpan().addAnnotation("my annotation");
+ * doSomeWork(); // Here the new span is in the current Context, so it can be used
+ * // implicitly anywhere down the stack. Anytime in this closure the span
+ * // can be accessed via tracer.getCurrentSpan().
+ * } finally {
+ * ss.close();
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>WARNING: The try-with-resources feature to auto-close spans as described above can sound
+ * very tempting due to its convenience, but it comes with an important and easy-to-miss
+ * trade-off: the span will be closed before any {@code catch} or {@code finally} blocks get a
+ * chance to execute. So if you need to catch any exceptions and log information about them (for
+ * example), then you do not want to use the try-with-resources shortcut because that logging will
+ * not be tagged with the span info of the span it logically falls under, and if you try to
+ * retrieve {@code Tracer.getCurrentSpan()} then you'll either get the parent span if one exists
+ * or {@code BlankSpan} if there was no parent span. This can be confusing and seem
+ * counter-intuitive, but it's the way try-with-resources works.
+ *
+ * @return an object that defines a scope where the newly created {@code Span} will be set to the
+ * current Context.
+ * @since 0.5
+ */
+ @MustBeClosed
+ public final Scope startScopedSpan() {
+ return CurrentSpanUtils.withSpan(startSpan(), /* endSpan= */ true);
+ }
+
+ /**
+ * Starts a new span and runs the given {@code Runnable} with the newly created {@code Span} as
+ * the current {@code Span}, and ends the {@code Span} after the {@code Runnable} is run.
+ *
+ * <p>Any error will end up as a {@link Status#UNKNOWN}.
+ *
+ * <pre><code>
+ * tracer.spanBuilder("MyRunnableSpan").startSpanAndRun(myRunnable);
+ * </code></pre>
+ *
+ * <p>It is equivalent with the following code:
+ *
+ * <pre><code>
+ * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan();
+ * Runnable newRunnable = tracer.withSpan(span, myRunnable);
+ * try {
+ * newRunnable.run();
+ * } finally {
+ * span.end();
+ * }
+ * </code></pre>
+ *
+ * @param runnable the {@code Runnable} to run in the {@code Span}.
+ * @since 0.11.0
+ */
+ public final void startSpanAndRun(final Runnable runnable) {
+ final Span span = startSpan();
+ CurrentSpanUtils.withSpan(span, /* endSpan= */ true, runnable).run();
+ }
+
+ /**
+ * Starts a new span and calls the given {@code Callable} with the newly created {@code Span} as
+ * the current {@code Span}, and ends the {@code Span} after the {@code Callable} is called.
+ *
+ * <p>Any error will end up as a {@link Status#UNKNOWN}.
+ *
+ * <pre><code>
+ * MyResult myResult = tracer.spanBuilder("MyCallableSpan").startSpanAndCall(myCallable);
+ * </code></pre>
+ *
+ * <p>It is equivalent with the following code:
+ *
+ * <pre><code>
+ * Span span = tracer.spanBuilder("MyCallableSpan").startSpan();
+ * {@code Callable<MyResult>} newCallable = tracer.withSpan(span, myCallable);
+ * MyResult myResult = null;
+ * try {
+ * myResult = newCallable.call();
+ * } finally {
+ * span.end();
+ * }
+ * );
+ * </code></pre>
+ *
+ * @param callable the {@code Callable} to run in the {@code Span}.
+ * @since 0.11.0
+ */
+ public final <V> V startSpanAndCall(Callable<V> callable) throws Exception {
+ final Span span = startSpan();
+ return CurrentSpanUtils.withSpan(span, /* endSpan= */ true, callable).call();
+ }
+
+ static final class NoopSpanBuilder extends SpanBuilder {
+ static NoopSpanBuilder createWithParent(String spanName, @Nullable Span parent) {
+ return new NoopSpanBuilder(spanName);
+ }
+
+ static NoopSpanBuilder createWithRemoteParent(
+ String spanName, @Nullable SpanContext remoteParentSpanContext) {
+ return new NoopSpanBuilder(spanName);
+ }
+
+ @Override
+ public Span startSpan() {
+ return BlankSpan.INSTANCE;
+ }
+
+ @Override
+ public SpanBuilder setSampler(@Nullable Sampler sampler) {
+ return this;
+ }
+
+ @Override
+ public SpanBuilder setParentLinks(List<Span> parentLinks) {
+ return this;
+ }
+
+ @Override
+ public SpanBuilder setRecordEvents(boolean recordEvents) {
+ return this;
+ }
+
+ @Override
+ public SpanBuilder setSpanKind(@Nullable Span.Kind spanKind) {
+ return this;
+ }
+
+ private NoopSpanBuilder(String name) {
+ Utils.checkNotNull(name, "name");
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/SpanContext.java b/api/src/main/java/io/opencensus/trace/SpanContext.java
new file mode 100644
index 00000000..49ed751b
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/SpanContext.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import java.util.Arrays;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that represents a span context. A span context contains the state that must propagate to
+ * child {@link Span}s and across process boundaries. It contains the identifiers (a {@link TraceId
+ * trace_id} and {@link SpanId span_id}) associated with the {@link Span} and a set of {@link
+ * TraceOptions options}.
+ *
+ * @since 0.5
+ */
+@Immutable
+public final class SpanContext {
+ private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build();
+ private final TraceId traceId;
+ private final SpanId spanId;
+ private final TraceOptions traceOptions;
+ private final Tracestate tracestate;
+
+ /**
+ * The invalid {@code SpanContext}.
+ *
+ * @since 0.5
+ */
+ public static final SpanContext INVALID =
+ new SpanContext(TraceId.INVALID, SpanId.INVALID, TraceOptions.DEFAULT, TRACESTATE_DEFAULT);
+
+ /**
+ * Creates a new {@code SpanContext} with the given identifiers and options.
+ *
+ * @param traceId the trace identifier of the span context.
+ * @param spanId the span identifier of the span context.
+ * @param traceOptions the trace options for the span context.
+ * @return a new {@code SpanContext} with the given identifiers and options.
+ * @deprecated use {@link #create(TraceId, SpanId, TraceOptions, Tracestate)}.
+ */
+ @Deprecated
+ public static SpanContext create(TraceId traceId, SpanId spanId, TraceOptions traceOptions) {
+ return create(traceId, spanId, traceOptions, TRACESTATE_DEFAULT);
+ }
+
+ /**
+ * Creates a new {@code SpanContext} with the given identifiers and options.
+ *
+ * @param traceId the trace identifier of the span context.
+ * @param spanId the span identifier of the span context.
+ * @param traceOptions the trace options for the span context.
+ * @param tracestate the trace state for the span context.
+ * @return a new {@code SpanContext} with the given identifiers and options.
+ * @since 0.16
+ */
+ public static SpanContext create(
+ TraceId traceId, SpanId spanId, TraceOptions traceOptions, Tracestate tracestate) {
+ return new SpanContext(traceId, spanId, traceOptions, tracestate);
+ }
+
+ /**
+ * Returns the trace identifier associated with this {@code SpanContext}.
+ *
+ * @return the trace identifier associated with this {@code SpanContext}.
+ * @since 0.5
+ */
+ public TraceId getTraceId() {
+ return traceId;
+ }
+
+ /**
+ * Returns the span identifier associated with this {@code SpanContext}.
+ *
+ * @return the span identifier associated with this {@code SpanContext}.
+ * @since 0.5
+ */
+ public SpanId getSpanId() {
+ return spanId;
+ }
+
+ /**
+ * Returns the {@code TraceOptions} associated with this {@code SpanContext}.
+ *
+ * @return the {@code TraceOptions} associated with this {@code SpanContext}.
+ * @since 0.5
+ */
+ public TraceOptions getTraceOptions() {
+ return traceOptions;
+ }
+
+ /**
+ * Returns the {@code Tracestate} associated with this {@code SpanContext}.
+ *
+ * @return the {@code Tracestate} associated with this {@code SpanContext}.
+ * @since 0.5
+ */
+ public Tracestate getTracestate() {
+ return tracestate;
+ }
+
+ /**
+ * Returns true if this {@code SpanContext} is valid.
+ *
+ * @return true if this {@code SpanContext} is valid.
+ * @since 0.5
+ */
+ public boolean isValid() {
+ return traceId.isValid() && spanId.isValid();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof SpanContext)) {
+ return false;
+ }
+
+ SpanContext that = (SpanContext) obj;
+ return traceId.equals(that.traceId)
+ && spanId.equals(that.spanId)
+ && traceOptions.equals(that.traceOptions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[] {traceId, spanId, traceOptions});
+ }
+
+ @Override
+ public String toString() {
+ return "SpanContext{traceId="
+ + traceId
+ + ", spanId="
+ + spanId
+ + ", traceOptions="
+ + traceOptions
+ + "}";
+ }
+
+ private SpanContext(
+ TraceId traceId, SpanId spanId, TraceOptions traceOptions, Tracestate tracestate) {
+ this.traceId = traceId;
+ this.spanId = spanId;
+ this.traceOptions = traceOptions;
+ this.tracestate = tracestate;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/SpanId.java b/api/src/main/java/io/opencensus/trace/SpanId.java
new file mode 100644
index 00000000..c43fa6b0
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/SpanId.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import io.opencensus.internal.Utils;
+import java.util.Arrays;
+import java.util.Random;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that represents a span identifier. A valid span identifier is an 8-byte array with at
+ * least one non-zero byte.
+ *
+ * @since 0.5
+ */
+@Immutable
+public final class SpanId implements Comparable<SpanId> {
+ /**
+ * The size in bytes of the {@code SpanId}.
+ *
+ * @since 0.5
+ */
+ public static final int SIZE = 8;
+
+ private static final int HEX_SIZE = 2 * SIZE;
+
+ /**
+ * The invalid {@code SpanId}. All bytes are 0.
+ *
+ * @since 0.5
+ */
+ public static final SpanId INVALID = new SpanId(new byte[SIZE]);
+
+ // The internal representation of the SpanId.
+ private final byte[] bytes;
+
+ private SpanId(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * Returns a {@code SpanId} built from a byte representation.
+ *
+ * <p>Equivalent with:
+ *
+ * <pre>{@code
+ * SpanId.fromBytes(buffer, 0);
+ * }</pre>
+ *
+ * @param buffer the representation of the {@code SpanId}.
+ * @return a {@code SpanId} whose representation is given by the {@code buffer} parameter.
+ * @throws NullPointerException if {@code buffer} is null.
+ * @throws IllegalArgumentException if {@code buffer.length} is not {@link SpanId#SIZE}.
+ * @since 0.5
+ */
+ public static SpanId fromBytes(byte[] buffer) {
+ Utils.checkNotNull(buffer, "buffer");
+ Utils.checkArgument(
+ buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length);
+ byte[] bytesCopied = Arrays.copyOf(buffer, SIZE);
+ return new SpanId(bytesCopied);
+ }
+
+ /**
+ * Returns a {@code SpanId} whose representation is copied from the {@code src} beginning at the
+ * {@code srcOffset} offset.
+ *
+ * @param src the buffer where the representation of the {@code SpanId} is copied.
+ * @param srcOffset the offset in the buffer where the representation of the {@code SpanId}
+ * begins.
+ * @return a {@code SpanId} whose representation is copied from the buffer.
+ * @throws NullPointerException if {@code src} is null.
+ * @throws IndexOutOfBoundsException if {@code srcOffset+SpanId.SIZE} is greater than {@code
+ * src.length}.
+ * @since 0.5
+ */
+ public static SpanId fromBytes(byte[] src, int srcOffset) {
+ byte[] bytes = new byte[SIZE];
+ System.arraycopy(src, srcOffset, bytes, 0, SIZE);
+ return new SpanId(bytes);
+ }
+
+ /**
+ * Returns a {@code SpanId} built from a lowercase base16 representation.
+ *
+ * @param src the lowercase base16 representation.
+ * @return a {@code SpanId} built from a lowercase base16 representation.
+ * @throws NullPointerException if {@code src} is null.
+ * @throws IllegalArgumentException if {@code src.length} is not {@code 2 * SpanId.SIZE} OR if the
+ * {@code str} has invalid characters.
+ * @since 0.11
+ */
+ public static SpanId fromLowerBase16(CharSequence src) {
+ Utils.checkArgument(
+ src.length() == HEX_SIZE, "Invalid size: expected %s, got %s", HEX_SIZE, src.length());
+ return new SpanId(LowerCaseBase16Encoding.decodeToBytes(src));
+ }
+
+ /**
+ * Generates a new random {@code SpanId}.
+ *
+ * @param random The random number generator.
+ * @return a valid new {@code SpanId}.
+ * @since 0.5
+ */
+ public static SpanId generateRandomId(Random random) {
+ byte[] bytes = new byte[SIZE];
+ do {
+ random.nextBytes(bytes);
+ } while (Arrays.equals(bytes, INVALID.bytes));
+ return new SpanId(bytes);
+ }
+
+ /**
+ * Returns the byte representation of the {@code SpanId}.
+ *
+ * @return the byte representation of the {@code SpanId}.
+ * @since 0.5
+ */
+ public byte[] getBytes() {
+ return Arrays.copyOf(bytes, SIZE);
+ }
+
+ /**
+ * Copies the byte array representations of the {@code SpanId} into the {@code dest} beginning at
+ * the {@code destOffset} offset.
+ *
+ * <p>Equivalent with (but faster because it avoids any new allocations):
+ *
+ * <pre>{@code
+ * System.arraycopy(getBytes(), 0, dest, destOffset, SpanId.SIZE);
+ * }</pre>
+ *
+ * @param dest the destination buffer.
+ * @param destOffset the starting offset in the destination buffer.
+ * @throws NullPointerException if {@code dest} is null.
+ * @throws IndexOutOfBoundsException if {@code destOffset+SpanId.SIZE} is greater than {@code
+ * dest.length}.
+ * @since 0.5
+ */
+ public void copyBytesTo(byte[] dest, int destOffset) {
+ System.arraycopy(bytes, 0, dest, destOffset, SIZE);
+ }
+
+ /**
+ * Returns whether the span identifier is valid. A valid span identifier is an 8-byte array with
+ * at least one non-zero byte.
+ *
+ * @return {@code true} if the span identifier is valid.
+ * @since 0.5
+ */
+ public boolean isValid() {
+ return !Arrays.equals(bytes, INVALID.bytes);
+ }
+
+ /**
+ * Returns the lowercase base16 encoding of this {@code SpanId}.
+ *
+ * @return the lowercase base16 encoding of this {@code SpanId}.
+ * @since 0.11
+ */
+ public String toLowerBase16() {
+ return LowerCaseBase16Encoding.encodeToString(bytes);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof SpanId)) {
+ return false;
+ }
+
+ SpanId that = (SpanId) obj;
+ return Arrays.equals(bytes, that.bytes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(bytes);
+ }
+
+ @Override
+ public String toString() {
+ return "SpanId{spanId=" + toLowerBase16() + "}";
+ }
+
+ @Override
+ public int compareTo(SpanId that) {
+ for (int i = 0; i < SIZE; i++) {
+ if (bytes[i] != that.bytes[i]) {
+ return bytes[i] < that.bytes[i] ? -1 : 1;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/Status.java b/api/src/main/java/io/opencensus/trace/Status.java
new file mode 100644
index 00000000..1fa85085
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Status.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import io.opencensus.internal.Utils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeMap;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/*>>>
+import org.checkerframework.dataflow.qual.Deterministic;
+*/
+
+/**
+ * Defines the status of a {@link Span} by providing a standard {@link CanonicalCode} in conjunction
+ * with an optional descriptive message. Instances of {@code Status} are created by starting with
+ * the template for the appropriate {@link Status.CanonicalCode} and supplementing it with
+ * additional information: {@code Status.NOT_FOUND.withDescription("Could not find
+ * 'important_file.txt'");}
+ *
+ * @since 0.5
+ */
+@Immutable
+public final class Status {
+ /**
+ * The set of canonical status codes. If new codes are added over time they must choose a
+ * numerical value that does not collide with any previously used value.
+ *
+ * @since 0.5
+ */
+ public enum CanonicalCode {
+ /**
+ * The operation completed successfully.
+ *
+ * @since 0.5
+ */
+ OK(0),
+
+ /**
+ * The operation was cancelled (typically by the caller).
+ *
+ * @since 0.5
+ */
+ CANCELLED(1),
+
+ /**
+ * Unknown error. An example of where this error may be returned is if a Status value received
+ * from another address space belongs to an error-space that is not known in this address space.
+ * Also errors raised by APIs that do not return enough error information may be converted to
+ * this error.
+ *
+ * @since 0.5
+ */
+ UNKNOWN(2),
+
+ /**
+ * Client specified an invalid argument. Note that this differs from FAILED_PRECONDITION.
+ * INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the
+ * system (e.g., a malformed file name).
+ *
+ * @since 0.5
+ */
+ INVALID_ARGUMENT(3),
+
+ /**
+ * Deadline expired before operation could complete. For operations that change the state of the
+ * system, this error may be returned even if the operation has completed successfully. For
+ * example, a successful response from a server could have been delayed long enough for the
+ * deadline to expire.
+ *
+ * @since 0.5
+ */
+ DEADLINE_EXCEEDED(4),
+
+ /**
+ * Some requested entity (e.g., file or directory) was not found.
+ *
+ * @since 0.5
+ */
+ NOT_FOUND(5),
+
+ /**
+ * Some entity that we attempted to create (e.g., file or directory) already exists.
+ *
+ * @since 0.5
+ */
+ ALREADY_EXISTS(6),
+
+ /**
+ * The caller does not have permission to execute the specified operation. PERMISSION_DENIED
+ * must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED
+ * instead for those errors). PERMISSION_DENIED must not be used if the caller cannot be
+ * identified (use UNAUTHENTICATED instead for those errors).
+ *
+ * @since 0.5
+ */
+ PERMISSION_DENIED(7),
+
+ /**
+ * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
+ * is out of space.
+ *
+ * @since 0.5
+ */
+ RESOURCE_EXHAUSTED(8),
+
+ /**
+ * Operation was rejected because the system is not in a state required for the operation's
+ * execution. For example, directory to be deleted may be non-empty, an rmdir operation is
+ * applied to a non-directory, etc.
+ *
+ * <p>A litmus test that may help a service implementor in deciding between FAILED_PRECONDITION,
+ * ABORTED, and UNAVAILABLE: (a) Use UNAVAILABLE if the client can retry just the failing call.
+ * (b) Use ABORTED if the client should retry at a higher-level (e.g., restarting a
+ * read-modify-write sequence). (c) Use FAILED_PRECONDITION if the client should not retry until
+ * the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory
+ * is non-empty, FAILED_PRECONDITION should be returned since the client should not retry unless
+ * they have first fixed up the directory by deleting files from it.
+ *
+ * @since 0.5
+ */
+ FAILED_PRECONDITION(9),
+
+ /**
+ * The operation was aborted, typically due to a concurrency issue like sequencer check
+ * failures, transaction aborts, etc.
+ *
+ * <p>See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE.
+ *
+ * @since 0.5
+ */
+ ABORTED(10),
+
+ /**
+ * Operation was attempted past the valid range. E.g., seeking or reading past end of file.
+ *
+ * <p>Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system
+ * state changes. For example, a 32-bit file system will generate INVALID_ARGUMENT if asked to
+ * read at an offset that is not in the range [0,2^32-1], but it will generate OUT_OF_RANGE if
+ * asked to read from an offset past the current file size.
+ *
+ * <p>There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE. We recommend
+ * using OUT_OF_RANGE (the more specific error) when it applies so that callers who are
+ * iterating through a space can easily look for an OUT_OF_RANGE error to detect when they are
+ * done.
+ *
+ * @since 0.5
+ */
+ OUT_OF_RANGE(11),
+
+ /**
+ * Operation is not implemented or not supported/enabled in this service.
+ *
+ * @since 0.5
+ */
+ UNIMPLEMENTED(12),
+
+ /**
+ * Internal errors. Means some invariants expected by underlying system has been broken. If you
+ * see one of these errors, something is very broken.
+ *
+ * @since 0.5
+ */
+ INTERNAL(13),
+
+ /**
+ * The service is currently unavailable. This is a most likely a transient condition and may be
+ * corrected by retrying with a backoff.
+ *
+ * <p>See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE.
+ *
+ * @since 0.5
+ */
+ UNAVAILABLE(14),
+
+ /**
+ * Unrecoverable data loss or corruption.
+ *
+ * @since 0.5
+ */
+ DATA_LOSS(15),
+
+ /**
+ * The request does not have valid authentication credentials for the operation.
+ *
+ * @since 0.5
+ */
+ UNAUTHENTICATED(16);
+
+ private final int value;
+
+ private CanonicalCode(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the numerical value of the code.
+ *
+ * @return the numerical value of the code.
+ * @since 0.5
+ */
+ public int value() {
+ return value;
+ }
+
+ /**
+ * Returns the status that has the current {@code CanonicalCode}..
+ *
+ * @return the status that has the current {@code CanonicalCode}.
+ * @since 0.5
+ */
+ public Status toStatus() {
+ return STATUS_LIST.get(value);
+ }
+ }
+
+ // Create the canonical list of Status instances indexed by their code values.
+ private static final List<Status> STATUS_LIST = buildStatusList();
+
+ private static List<Status> buildStatusList() {
+ TreeMap<Integer, Status> canonicalizer = new TreeMap<Integer, Status>();
+ for (CanonicalCode code : CanonicalCode.values()) {
+ Status replaced = canonicalizer.put(code.value(), new Status(code, null));
+ if (replaced != null) {
+ throw new IllegalStateException(
+ "Code value duplication between "
+ + replaced.getCanonicalCode().name()
+ + " & "
+ + code.name());
+ }
+ }
+ return Collections.unmodifiableList(new ArrayList<Status>(canonicalizer.values()));
+ }
+
+ // A pseudo-enum of Status instances mapped 1:1 with values in CanonicalCode. This simplifies
+ // construction patterns for derived instances of Status.
+ /**
+ * The operation completed successfully.
+ *
+ * @since 0.5
+ */
+ public static final Status OK = CanonicalCode.OK.toStatus();
+
+ /**
+ * The operation was cancelled (typically by the caller).
+ *
+ * @since 0.5
+ */
+ public static final Status CANCELLED = CanonicalCode.CANCELLED.toStatus();
+
+ /**
+ * Unknown error. See {@link CanonicalCode#UNKNOWN}.
+ *
+ * @since 0.5
+ */
+ public static final Status UNKNOWN = CanonicalCode.UNKNOWN.toStatus();
+
+ /**
+ * Client specified an invalid argument. See {@link CanonicalCode#INVALID_ARGUMENT}.
+ *
+ * @since 0.5
+ */
+ public static final Status INVALID_ARGUMENT = CanonicalCode.INVALID_ARGUMENT.toStatus();
+
+ /**
+ * Deadline expired before operation could complete. See {@link CanonicalCode#DEADLINE_EXCEEDED}.
+ *
+ * @since 0.5
+ */
+ public static final Status DEADLINE_EXCEEDED = CanonicalCode.DEADLINE_EXCEEDED.toStatus();
+
+ /**
+ * Some requested entity (e.g., file or directory) was not found.
+ *
+ * @since 0.5
+ */
+ public static final Status NOT_FOUND = CanonicalCode.NOT_FOUND.toStatus();
+
+ /**
+ * Some entity that we attempted to create (e.g., file or directory) already exists.
+ *
+ * @since 0.5
+ */
+ public static final Status ALREADY_EXISTS = CanonicalCode.ALREADY_EXISTS.toStatus();
+
+ /**
+ * The caller does not have permission to execute the specified operation. See {@link
+ * CanonicalCode#PERMISSION_DENIED}.
+ *
+ * @since 0.5
+ */
+ public static final Status PERMISSION_DENIED = CanonicalCode.PERMISSION_DENIED.toStatus();
+
+ /**
+ * The request does not have valid authentication credentials for the operation.
+ *
+ * @since 0.5
+ */
+ public static final Status UNAUTHENTICATED = CanonicalCode.UNAUTHENTICATED.toStatus();
+
+ /**
+ * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
+ * is out of space.
+ *
+ * @since 0.5
+ */
+ public static final Status RESOURCE_EXHAUSTED = CanonicalCode.RESOURCE_EXHAUSTED.toStatus();
+
+ /**
+ * Operation was rejected because the system is not in a state required for the operation's
+ * execution. See {@link CanonicalCode#FAILED_PRECONDITION}.
+ *
+ * @since 0.5
+ */
+ public static final Status FAILED_PRECONDITION = CanonicalCode.FAILED_PRECONDITION.toStatus();
+
+ /**
+ * The operation was aborted, typically due to a concurrency issue like sequencer check failures,
+ * transaction aborts, etc. See {@link CanonicalCode#ABORTED}.
+ *
+ * @since 0.5
+ */
+ public static final Status ABORTED = CanonicalCode.ABORTED.toStatus();
+
+ /**
+ * Operation was attempted past the valid range. See {@link CanonicalCode#OUT_OF_RANGE}.
+ *
+ * @since 0.5
+ */
+ public static final Status OUT_OF_RANGE = CanonicalCode.OUT_OF_RANGE.toStatus();
+
+ /**
+ * Operation is not implemented or not supported/enabled in this service.
+ *
+ * @since 0.5
+ */
+ public static final Status UNIMPLEMENTED = CanonicalCode.UNIMPLEMENTED.toStatus();
+
+ /**
+ * Internal errors. See {@link CanonicalCode#INTERNAL}.
+ *
+ * @since 0.5
+ */
+ public static final Status INTERNAL = CanonicalCode.INTERNAL.toStatus();
+
+ /**
+ * The service is currently unavailable. See {@link CanonicalCode#UNAVAILABLE}.
+ *
+ * @since 0.5
+ */
+ public static final Status UNAVAILABLE = CanonicalCode.UNAVAILABLE.toStatus();
+
+ /**
+ * Unrecoverable data loss or corruption.
+ *
+ * @since 0.5
+ */
+ public static final Status DATA_LOSS = CanonicalCode.DATA_LOSS.toStatus();
+
+ // The canonical code of this message.
+ private final CanonicalCode canonicalCode;
+
+ // An additional error message.
+ @Nullable private final String description;
+
+ private Status(CanonicalCode canonicalCode, @Nullable String description) {
+ this.canonicalCode = Utils.checkNotNull(canonicalCode, "canonicalCode");
+ this.description = description;
+ }
+
+ /**
+ * Creates a derived instance of {@code Status} with the given description.
+ *
+ * @param description the new description of the {@code Status}.
+ * @return The newly created {@code Status} with the given description.
+ * @since 0.5
+ */
+ public Status withDescription(String description) {
+ if (Utils.equalsObjects(this.description, description)) {
+ return this;
+ }
+ return new Status(this.canonicalCode, description);
+ }
+
+ /**
+ * Returns the canonical status code.
+ *
+ * @return the canonical status code.
+ * @since 0.5
+ */
+ public CanonicalCode getCanonicalCode() {
+ return canonicalCode;
+ }
+
+ /**
+ * Returns the description of this {@code Status} for human consumption.
+ *
+ * @return the description of this {@code Status}.
+ * @since 0.5
+ */
+ @Nullable
+ /*@Deterministic*/
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Returns {@code true} if this {@code Status} is OK, i.e., not an error.
+ *
+ * @return {@code true} if this {@code Status} is OK.
+ * @since 0.5
+ */
+ public boolean isOk() {
+ return CanonicalCode.OK == canonicalCode;
+ }
+
+ /**
+ * Equality on Statuses is not well defined. Instead, do comparison based on their CanonicalCode
+ * with {@link #getCanonicalCode}. The description of the Status is unlikely to be stable, and
+ * additional fields may be added to Status in the future.
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof Status)) {
+ return false;
+ }
+
+ Status that = (Status) obj;
+ return canonicalCode == that.canonicalCode
+ && Utils.equalsObjects(description, that.description);
+ }
+
+ /**
+ * Hash codes on Statuses are not well defined.
+ *
+ * @see #equals
+ */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new Object[] {canonicalCode, description});
+ }
+
+ @Override
+ public String toString() {
+ return "Status{canonicalCode=" + canonicalCode + ", description=" + description + "}";
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/TraceComponent.java b/api/src/main/java/io/opencensus/trace/TraceComponent.java
new file mode 100644
index 00000000..d98d0f9e
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/TraceComponent.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import io.opencensus.common.Clock;
+import io.opencensus.internal.ZeroTimeClock;
+import io.opencensus.trace.config.TraceConfig;
+import io.opencensus.trace.export.ExportComponent;
+import io.opencensus.trace.propagation.PropagationComponent;
+
+/**
+ * Class that holds the implementation instances for {@link Tracer}, {@link PropagationComponent},
+ * {@link Clock}, {@link ExportComponent} and {@link TraceConfig}.
+ *
+ * <p>Unless otherwise noted all methods (on component) results are cacheable.
+ *
+ * @since 0.5
+ */
+public abstract class TraceComponent {
+
+ /**
+ * Returns the {@link Tracer} with the provided implementations. If no implementation is provided
+ * then no-op implementations will be used.
+ *
+ * @return the {@code Tracer} implementation.
+ * @since 0.5
+ */
+ public abstract Tracer getTracer();
+
+ /**
+ * Returns the {@link PropagationComponent} with the provided implementation. If no implementation
+ * is provided then no-op implementation will be used.
+ *
+ * @return the {@code PropagationComponent} implementation.
+ * @since 0.5
+ */
+ public abstract PropagationComponent getPropagationComponent();
+
+ /**
+ * Returns the {@link Clock} with the provided implementation.
+ *
+ * @return the {@code Clock} implementation.
+ * @since 0.5
+ */
+ public abstract Clock getClock();
+
+ /**
+ * Returns the {@link ExportComponent} with the provided implementation. If no implementation is
+ * provided then no-op implementations will be used.
+ *
+ * @return the {@link ExportComponent} implementation.
+ * @since 0.5
+ */
+ public abstract ExportComponent getExportComponent();
+
+ /**
+ * Returns the {@link TraceConfig} with the provided implementation. If no implementation is
+ * provided then no-op implementations will be used.
+ *
+ * @return the {@link TraceConfig} implementation.
+ * @since 0.5
+ */
+ public abstract TraceConfig getTraceConfig();
+
+ /**
+ * Returns an instance that contains no-op implementations for all the instances.
+ *
+ * @return an instance that contains no-op implementations for all the instances.
+ */
+ static TraceComponent newNoopTraceComponent() {
+ return new NoopTraceComponent();
+ }
+
+ private static final class NoopTraceComponent extends TraceComponent {
+ private final ExportComponent noopExportComponent = ExportComponent.newNoopExportComponent();
+
+ @Override
+ public Tracer getTracer() {
+ return Tracer.getNoopTracer();
+ }
+
+ @Override
+ public PropagationComponent getPropagationComponent() {
+ return PropagationComponent.getNoopPropagationComponent();
+ }
+
+ @Override
+ public Clock getClock() {
+ return ZeroTimeClock.getInstance();
+ }
+
+ @Override
+ public ExportComponent getExportComponent() {
+ return noopExportComponent;
+ }
+
+ @Override
+ public TraceConfig getTraceConfig() {
+ return TraceConfig.getNoopTraceConfig();
+ }
+
+ private NoopTraceComponent() {}
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/TraceId.java b/api/src/main/java/io/opencensus/trace/TraceId.java
new file mode 100644
index 00000000..465e4d4a
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/TraceId.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import io.opencensus.common.Internal;
+import io.opencensus.internal.Utils;
+import java.util.Arrays;
+import java.util.Random;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that represents a trace identifier. A valid trace identifier is a 16-byte array with at
+ * least one non-zero byte.
+ *
+ * @since 0.5
+ */
+@Immutable
+public final class TraceId implements Comparable<TraceId> {
+ /**
+ * The size in bytes of the {@code TraceId}.
+ *
+ * @since 0.5
+ */
+ public static final int SIZE = 16;
+
+ private static final int HEX_SIZE = 32;
+
+ /**
+ * The invalid {@code TraceId}. All bytes are '\0'.
+ *
+ * @since 0.5
+ */
+ public static final TraceId INVALID = new TraceId(new byte[SIZE]);
+
+ // The internal representation of the TraceId.
+ private final byte[] bytes;
+
+ private TraceId(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * Returns a {@code TraceId} built from a byte representation.
+ *
+ * <p>Equivalent with:
+ *
+ * <pre>{@code
+ * TraceId.fromBytes(buffer, 0);
+ * }</pre>
+ *
+ * @param buffer the representation of the {@code TraceId}.
+ * @return a {@code TraceId} whose representation is given by the {@code buffer} parameter.
+ * @throws NullPointerException if {@code buffer} is null.
+ * @throws IllegalArgumentException if {@code buffer.length} is not {@link TraceId#SIZE}.
+ * @since 0.5
+ */
+ public static TraceId fromBytes(byte[] buffer) {
+ Utils.checkNotNull(buffer, "buffer");
+ Utils.checkArgument(
+ buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length);
+ byte[] bytesCopied = Arrays.copyOf(buffer, SIZE);
+ return new TraceId(bytesCopied);
+ }
+
+ /**
+ * Returns a {@code TraceId} whose representation is copied from the {@code src} beginning at the
+ * {@code srcOffset} offset.
+ *
+ * @param src the buffer where the representation of the {@code TraceId} is copied.
+ * @param srcOffset the offset in the buffer where the representation of the {@code TraceId}
+ * begins.
+ * @return a {@code TraceId} whose representation is copied from the buffer.
+ * @throws NullPointerException if {@code src} is null.
+ * @throws IndexOutOfBoundsException if {@code srcOffset+TraceId.SIZE} is greater than {@code
+ * src.length}.
+ * @since 0.5
+ */
+ public static TraceId fromBytes(byte[] src, int srcOffset) {
+ byte[] bytes = new byte[SIZE];
+ System.arraycopy(src, srcOffset, bytes, 0, SIZE);
+ return new TraceId(bytes);
+ }
+
+ /**
+ * Returns a {@code TraceId} built from a lowercase base16 representation.
+ *
+ * @param src the lowercase base16 representation.
+ * @return a {@code TraceId} built from a lowercase base16 representation.
+ * @throws NullPointerException if {@code src} is null.
+ * @throws IllegalArgumentException if {@code src.length} is not {@code 2 * TraceId.SIZE} OR if
+ * the {@code str} has invalid characters.
+ * @since 0.11
+ */
+ public static TraceId fromLowerBase16(CharSequence src) {
+ Utils.checkArgument(
+ src.length() == HEX_SIZE, "Invalid size: expected %s, got %s", HEX_SIZE, src.length());
+ return new TraceId(LowerCaseBase16Encoding.decodeToBytes(src));
+ }
+
+ /**
+ * Generates a new random {@code TraceId}.
+ *
+ * @param random the random number generator.
+ * @return a new valid {@code TraceId}.
+ * @since 0.5
+ */
+ public static TraceId generateRandomId(Random random) {
+ byte[] bytes = new byte[SIZE];
+ do {
+ random.nextBytes(bytes);
+ } while (Arrays.equals(bytes, INVALID.bytes));
+ return new TraceId(bytes);
+ }
+
+ /**
+ * Returns the 16-bytes array representation of the {@code TraceId}.
+ *
+ * @return the 16-bytes array representation of the {@code TraceId}.
+ * @since 0.5
+ */
+ public byte[] getBytes() {
+ return Arrays.copyOf(bytes, SIZE);
+ }
+
+ /**
+ * Copies the byte array representations of the {@code TraceId} into the {@code dest} beginning at
+ * the {@code destOffset} offset.
+ *
+ * <p>Equivalent with (but faster because it avoids any new allocations):
+ *
+ * <pre>{@code
+ * System.arraycopy(getBytes(), 0, dest, destOffset, TraceId.SIZE);
+ * }</pre>
+ *
+ * @param dest the destination buffer.
+ * @param destOffset the starting offset in the destination buffer.
+ * @throws NullPointerException if {@code dest} is null.
+ * @throws IndexOutOfBoundsException if {@code destOffset+TraceId.SIZE} is greater than {@code
+ * dest.length}.
+ * @since 0.5
+ */
+ public void copyBytesTo(byte[] dest, int destOffset) {
+ System.arraycopy(bytes, 0, dest, destOffset, SIZE);
+ }
+
+ /**
+ * Returns whether the {@code TraceId} is valid. A valid trace identifier is a 16-byte array with
+ * at least one non-zero byte.
+ *
+ * @return {@code true} if the {@code TraceId} is valid.
+ * @since 0.5
+ */
+ public boolean isValid() {
+ return !Arrays.equals(bytes, INVALID.bytes);
+ }
+
+ /**
+ * Returns the lowercase base16 encoding of this {@code TraceId}.
+ *
+ * @return the lowercase base16 encoding of this {@code TraceId}.
+ * @since 0.11
+ */
+ public String toLowerBase16() {
+ return LowerCaseBase16Encoding.encodeToString(bytes);
+ }
+
+ /**
+ * Returns the lower 8 bytes of the trace-id as a long value, assuming little-endian order. This
+ * is used in ProbabilitySampler.
+ *
+ * <p>This method is marked as internal and subject to change.
+ *
+ * @return the lower 8 bytes of the trace-id as a long value, assuming little-endian order.
+ */
+ @Internal
+ public long getLowerLong() {
+ long result = 0;
+ for (int i = 0; i < Long.SIZE / Byte.SIZE; i++) {
+ result <<= Byte.SIZE;
+ result |= (bytes[i] & 0xff);
+ }
+ if (result < 0) {
+ return -result;
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof TraceId)) {
+ return false;
+ }
+
+ TraceId that = (TraceId) obj;
+ return Arrays.equals(bytes, that.bytes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(bytes);
+ }
+
+ @Override
+ public String toString() {
+ return "TraceId{traceId=" + toLowerBase16() + "}";
+ }
+
+ @Override
+ public int compareTo(TraceId that) {
+ for (int i = 0; i < SIZE; i++) {
+ if (bytes[i] != that.bytes[i]) {
+ return bytes[i] < that.bytes[i] ? -1 : 1;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/TraceOptions.java b/api/src/main/java/io/opencensus/trace/TraceOptions.java
new file mode 100644
index 00000000..218f4dab
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/TraceOptions.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import io.opencensus.internal.DefaultVisibilityForTesting;
+import io.opencensus.internal.Utils;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A class that represents global trace options. These options are propagated to all child {@link
+ * io.opencensus.trace.Span spans}. These determine features such as whether a {@code Span} should
+ * be traced. It is implemented as a bitmask.
+ *
+ * @since 0.5
+ */
+@Immutable
+public final class TraceOptions {
+ // Default options. Nothing set.
+ private static final byte DEFAULT_OPTIONS = 0;
+ // Bit to represent whether trace is sampled or not.
+ private static final byte IS_SAMPLED = 0x1;
+
+ /**
+ * The size in bytes of the {@code TraceOptions}.
+ *
+ * @since 0.5
+ */
+ public static final int SIZE = 1;
+
+ /**
+ * The default {@code TraceOptions}.
+ *
+ * @since 0.5
+ */
+ public static final TraceOptions DEFAULT = fromByte(DEFAULT_OPTIONS);
+
+ // The set of enabled features is determined by all the enabled bits.
+ private final byte options;
+
+ // Creates a new {@code TraceOptions} with the given options.
+ private TraceOptions(byte options) {
+ this.options = options;
+ }
+
+ /**
+ * Returns a {@code TraceOptions} built from a byte representation.
+ *
+ * <p>Equivalent with:
+ *
+ * <pre>{@code
+ * TraceOptions.fromBytes(buffer, 0);
+ * }</pre>
+ *
+ * @param buffer the representation of the {@code TraceOptions}.
+ * @return a {@code TraceOptions} whose representation is given by the {@code buffer} parameter.
+ * @throws NullPointerException if {@code buffer} is null.
+ * @throws IllegalArgumentException if {@code buffer.length} is not {@link TraceOptions#SIZE}.
+ * @since 0.5
+ * @deprecated use {@link #fromByte(byte)}.
+ */
+ @Deprecated
+ public static TraceOptions fromBytes(byte[] buffer) {
+ Utils.checkNotNull(buffer, "buffer");
+ Utils.checkArgument(
+ buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length);
+ return fromByte(buffer[0]);
+ }
+
+ /**
+ * Returns a {@code TraceOptions} whose representation is copied from the {@code src} beginning at
+ * the {@code srcOffset} offset.
+ *
+ * @param src the buffer where the representation of the {@code TraceOptions} is copied.
+ * @param srcOffset the offset in the buffer where the representation of the {@code TraceOptions}
+ * begins.
+ * @return a {@code TraceOptions} whose representation is copied from the buffer.
+ * @throws NullPointerException if {@code src} is null.
+ * @throws IndexOutOfBoundsException if {@code srcOffset+TraceOptions.SIZE} is greater than {@code
+ * src.length}.
+ * @since 0.5
+ * @deprecated use {@link #fromByte(byte)}.
+ */
+ @Deprecated
+ public static TraceOptions fromBytes(byte[] src, int srcOffset) {
+ Utils.checkIndex(srcOffset, src.length);
+ return fromByte(src[srcOffset]);
+ }
+
+ /**
+ * Returns a {@code TraceOptions} whose representation is {@code src}.
+ *
+ * @param src the byte representation of the {@code TraceOptions}.
+ * @return a {@code TraceOptions} whose representation is {@code src}.
+ * @since 0.16
+ */
+ public static TraceOptions fromByte(byte src) {
+ // TODO(bdrutu): OPTIMIZATION: Cache all the 256 possible objects and return from the cache.
+ return new TraceOptions(src);
+ }
+
+ /**
+ * Returns the one byte representation of the {@code TraceOptions}.
+ *
+ * @return the one byte representation of the {@code TraceOptions}.
+ * @since 0.16
+ */
+ public byte getByte() {
+ return options;
+ }
+
+ /**
+ * Returns the 1-byte array representation of the {@code TraceOptions}.
+ *
+ * @return the 1-byte array representation of the {@code TraceOptions}.
+ * @since 0.5
+ * @deprecated use {@link #getByte()}.
+ */
+ @Deprecated
+ public byte[] getBytes() {
+ byte[] bytes = new byte[SIZE];
+ bytes[0] = options;
+ return bytes;
+ }
+
+ /**
+ * Copies the byte representations of the {@code TraceOptions} into the {@code dest} beginning at
+ * the {@code destOffset} offset.
+ *
+ * <p>Equivalent with (but faster because it avoids any new allocations):
+ *
+ * <pre>{@code
+ * System.arraycopy(getBytes(), 0, dest, destOffset, TraceOptions.SIZE);
+ * }</pre>
+ *
+ * @param dest the destination buffer.
+ * @param destOffset the starting offset in the destination buffer.
+ * @throws NullPointerException if {@code dest} is null.
+ * @throws IndexOutOfBoundsException if {@code destOffset+TraceOptions.SIZE} is greater than
+ * {@code dest.length}.
+ * @since 0.5
+ */
+ public void copyBytesTo(byte[] dest, int destOffset) {
+ Utils.checkIndex(destOffset, dest.length);
+ dest[destOffset] = options;
+ }
+
+ /**
+ * Returns a new {@link Builder} with default options.
+ *
+ * @return a new {@code Builder} with default options.
+ * @since 0.5
+ */
+ public static Builder builder() {
+ return new Builder(DEFAULT_OPTIONS);
+ }
+
+ /**
+ * Returns a new {@link Builder} with all given options set.
+ *
+ * @param traceOptions the given options set.
+ * @return a new {@code Builder} with all given options set.
+ * @since 0.5
+ */
+ public static Builder builder(TraceOptions traceOptions) {
+ return new Builder(traceOptions.options);
+ }
+
+ /**
+ * Returns a boolean indicating whether this {@code Span} is part of a sampled trace and data
+ * should be exported to a persistent store.
+ *
+ * @return a boolean indicating whether the trace is sampled.
+ * @since 0.5
+ */
+ public boolean isSampled() {
+ return hasOption(IS_SAMPLED);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof TraceOptions)) {
+ return false;
+ }
+
+ TraceOptions that = (TraceOptions) obj;
+ return options == that.options;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(new byte[] {options});
+ }
+
+ @Override
+ public String toString() {
+ return "TraceOptions{sampled=" + isSampled() + "}";
+ }
+
+ /**
+ * Builder class for {@link TraceOptions}.
+ *
+ * @since 0.5
+ */
+ public static final class Builder {
+ private byte options;
+
+ private Builder(byte options) {
+ this.options = options;
+ }
+
+ /**
+ * Sets the sampling bit in the options to true.
+ *
+ * @deprecated Use {@code Builder.setIsSampled(true)}.
+ * @return this.
+ * @since 0.5
+ */
+ @Deprecated
+ public Builder setIsSampled() {
+ return setIsSampled(true);
+ }
+
+ /**
+ * Sets the sampling bit in the options.
+ *
+ * @param isSampled the sampling bit.
+ * @return this.
+ * @since 0.7
+ */
+ public Builder setIsSampled(boolean isSampled) {
+ if (isSampled) {
+ options = (byte) (options | IS_SAMPLED);
+ } else {
+ options = (byte) (options & ~IS_SAMPLED);
+ ;
+ }
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@code TraceOptions} with the desired options.
+ *
+ * @return a {@code TraceOptions} with the desired options.
+ * @since 0.5
+ */
+ public TraceOptions build() {
+ return fromByte(options);
+ }
+ }
+
+ // Returns the current set of options bitmask.
+ @DefaultVisibilityForTesting
+ byte getOptions() {
+ return options;
+ }
+
+ private boolean hasOption(int mask) {
+ return (this.options & mask) != 0;
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/Tracer.java b/api/src/main/java/io/opencensus/trace/Tracer.java
new file mode 100644
index 00000000..a2c0a239
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Tracer.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import com.google.errorprone.annotations.MustBeClosed;
+import io.opencensus.common.Scope;
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.SpanBuilder.NoopSpanBuilder;
+import java.util.concurrent.Callable;
+import javax.annotation.Nullable;
+
+/**
+ * Tracer is a simple, thin class for {@link Span} creation and in-process context interaction.
+ *
+ * <p>Users may choose to use manual or automatic Context propagation. Because of that this class
+ * offers APIs to facilitate both usages.
+ *
+ * <p>The automatic context propagation is done using {@link io.grpc.Context} which is a gRPC
+ * independent implementation for in-process Context propagation mechanism which can carry
+ * scoped-values across API boundaries and between threads. Users of the library must propagate the
+ * {@link io.grpc.Context} between different threads.
+ *
+ * <p>Example usage with automatic context propagation:
+ *
+ * <pre>{@code
+ * class MyClass {
+ * private static final Tracer tracer = Tracing.getTracer();
+ * void doWork() {
+ * try(Scope ss = tracer.spanBuilder("MyClass.DoWork").startScopedSpan()) {
+ * tracer.getCurrentSpan().addAnnotation("Starting the work.");
+ * doWorkInternal();
+ * tracer.getCurrentSpan().addAnnotation("Finished working.");
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>Example usage with manual context propagation:
+ *
+ * <pre>{@code
+ * class MyClass {
+ * private static final Tracer tracer = Tracing.getTracer();
+ * void doWork(Span parent) {
+ * Span childSpan = tracer.spanBuilderWithExplicitParent("MyChildSpan", parent).startSpan();
+ * childSpan.addAnnotation("Starting the work.");
+ * try {
+ * doSomeWork(childSpan); // Manually propagate the new span down the stack.
+ * } finally {
+ * // To make sure we end the span even in case of an exception.
+ * childSpan.end(); // Manually end the span.
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * @since 0.5
+ */
+public abstract class Tracer {
+ private static final NoopTracer noopTracer = new NoopTracer();
+
+ /**
+ * Returns the no-op implementation of the {@code Tracer}.
+ *
+ * @return the no-op implementation of the {@code Tracer}.
+ */
+ static Tracer getNoopTracer() {
+ return noopTracer;
+ }
+
+ /**
+ * Gets the current Span from the current Context.
+ *
+ * <p>To install a {@link Span} to the current Context use {@link #withSpan(Span)} OR use {@link
+ * SpanBuilder#startScopedSpan} methods to start a new {@code Span}.
+ *
+ * <p>startSpan methods do NOT modify the current Context {@code Span}.
+ *
+ * @return a default {@code Span} that does nothing and has an invalid {@link SpanContext} if no
+ * {@code Span} is associated with the current Context, otherwise the current {@code Span}
+ * from the Context.
+ * @since 0.5
+ */
+ public final Span getCurrentSpan() {
+ Span currentSpan = CurrentSpanUtils.getCurrentSpan();
+ return currentSpan != null ? currentSpan : BlankSpan.INSTANCE;
+ }
+
+ /**
+ * Enters the scope of code where the given {@link Span} is in the current Context, and returns an
+ * object that represents that scope. The scope is exited when the returned object is closed.
+ *
+ * <p>Supports try-with-resource idiom.
+ *
+ * <p>Can be called with {@link BlankSpan} to enter a scope of code where tracing is stopped.
+ *
+ * <p>Example of usage:
+ *
+ * <pre>{@code
+ * private static Tracer tracer = Tracing.getTracer();
+ * void doWork() {
+ * // Create a Span as a child of the current Span.
+ * Span span = tracer.spanBuilder("my span").startSpan();
+ * try (Scope ws = tracer.withSpan(span)) {
+ * tracer.getCurrentSpan().addAnnotation("my annotation");
+ * doSomeOtherWork(); // Here "span" is the current Span.
+ * }
+ * span.end();
+ * }
+ * }</pre>
+ *
+ * <p>Prior to Java SE 7, you can use a finally block to ensure that a resource is closed
+ * regardless of whether the try statement completes normally or abruptly.
+ *
+ * <p>Example of usage prior to Java SE7:
+ *
+ * <pre>{@code
+ * private static Tracer tracer = Tracing.getTracer();
+ * void doWork() {
+ * // Create a Span as a child of the current Span.
+ * Span span = tracer.spanBuilder("my span").startSpan();
+ * Scope ws = tracer.withSpan(span);
+ * try {
+ * tracer.getCurrentSpan().addAnnotation("my annotation");
+ * doSomeOtherWork(); // Here "span" is the current Span.
+ * } finally {
+ * ws.close();
+ * }
+ * span.end();
+ * }
+ * }</pre>
+ *
+ * @param span The {@link Span} to be set to the current Context.
+ * @return an object that defines a scope where the given {@link Span} will be set to the current
+ * Context.
+ * @throws NullPointerException if {@code span} is {@code null}.
+ * @since 0.5
+ */
+ @MustBeClosed
+ public final Scope withSpan(Span span) {
+ return CurrentSpanUtils.withSpan(Utils.checkNotNull(span, "span"), /* endSpan= */ false);
+ }
+
+ /**
+ * Returns a {@link Runnable} that runs the given task with the given {@code Span} in the current
+ * context.
+ *
+ * <p>Users may consider to use {@link SpanBuilder#startSpanAndRun(Runnable)}.
+ *
+ * <p>Any error will end up as a {@link Status#UNKNOWN}.
+ *
+ * <p>IMPORTANT: Caller must manually propagate the entire {@code io.grpc.Context} when wraps a
+ * {@code Runnable}, see the examples.
+ *
+ * <p>IMPORTANT: Caller must manually end the {@code Span} within the {@code Runnable}, or after
+ * the {@code Runnable} is executed.
+ *
+ * <p>Example with Executor wrapped with {@link io.grpc.Context#currentContextExecutor}:
+ *
+ * <pre><code>
+ * class MyClass {
+ * private static Tracer tracer = Tracing.getTracer();
+ * void handleRequest(Executor executor) {
+ * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan();
+ * executor.execute(tracer.withSpan(span, new Runnable() {
+ * {@literal @}Override
+ * public void run() {
+ * try {
+ * sendResult();
+ * } finally {
+ * span.end();
+ * }
+ * }
+ * }));
+ * }
+ * }
+ * </code></pre>
+ *
+ * <p>Example without Executor wrapped with {@link io.grpc.Context#currentContextExecutor}:
+ *
+ * <pre><code>
+ * class MyClass {
+ * private static Tracer tracer = Tracing.getTracer();
+ * void handleRequest(Executor executor) {
+ * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan();
+ * executor.execute(Context.wrap(tracer.withSpan(span, new Runnable() {
+ * {@literal @}Override
+ * public void run() {
+ * try {
+ * sendResult();
+ * } finally {
+ * span.end();
+ * }
+ * }
+ * })));
+ * }
+ * }
+ * </code></pre>
+ *
+ * @param span the {@code Span} to be set as current.
+ * @param runnable the {@code Runnable} to withSpan in the {@code Span}.
+ * @return the {@code Runnable}.
+ * @since 0.11.0
+ */
+ public final Runnable withSpan(Span span, Runnable runnable) {
+ return CurrentSpanUtils.withSpan(span, /* endSpan= */ false, runnable);
+ }
+
+ /**
+ * Returns a {@link Callable} that runs the given task with the given {@code Span} in the current
+ * context.
+ *
+ * <p>Users may consider to use {@link SpanBuilder#startSpanAndCall(Callable)}.
+ *
+ * <p>Any error will end up as a {@link Status#UNKNOWN}.
+ *
+ * <p>IMPORTANT: Caller must manually propagate the entire {@code io.grpc.Context} when wraps a
+ * {@code Callable}, see the examples.
+ *
+ * <p>IMPORTANT: Caller must manually end the {@code Span} within the {@code Callable}, or after
+ * the {@code Callable} is executed.
+ *
+ * <p>Example with Executor wrapped with {@link io.grpc.Context#currentContextExecutor}:
+ *
+ * <pre><code>
+ * class MyClass {
+ * private static Tracer tracer = Tracing.getTracer();
+ * void handleRequest(Executor executor) {
+ * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan();
+ * executor.execute(tracer.withSpan(span, {@code new Callable<MyResult>()} {
+ * {@literal @}Override
+ * public MyResult call() throws Exception {
+ * try {
+ * return sendResult();
+ * } finally {
+ * span.end();
+ * }
+ * }
+ * }));
+ * }
+ * }
+ * </code></pre>
+ *
+ * <p>Example without Executor wrapped with {@link io.grpc.Context#currentContextExecutor}:
+ *
+ * <pre><code>
+ * class MyClass {
+ * private static Tracer tracer = Tracing.getTracer();
+ * void handleRequest(Executor executor) {
+ * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan();
+ * executor.execute(Context.wrap(tracer.withSpan(span, {@code new Callable<MyResult>()} {
+ * {@literal @}Override
+ * public MyResult call() throws Exception {
+ * try {
+ * return sendResult();
+ * } finally {
+ * span.end();
+ * }
+ * }
+ * })));
+ * }
+ * }
+ * </code></pre>
+ *
+ * @param span the {@code Span} to be set as current.
+ * @param callable the {@code Callable} to run in the {@code Span}.
+ * @return the {@code Callable}.
+ * @since 0.11.0
+ */
+ public final <C> Callable<C> withSpan(Span span, final Callable<C> callable) {
+ return CurrentSpanUtils.withSpan(span, /* endSpan= */ false, callable);
+ }
+
+ /**
+ * Returns a {@link SpanBuilder} to create and start a new child {@link Span} as a child of to the
+ * current {@code Span} if any, otherwise creates a root {@code Span}.
+ *
+ * <p>See {@link SpanBuilder} for usage examples.
+ *
+ * <p>This <b>must</b> be used to create a {@code Span} when automatic Context propagation is
+ * used.
+ *
+ * <p>This is equivalent with:
+ *
+ * <pre>{@code
+ * tracer.spanBuilderWithExplicitParent("MySpanName",tracer.getCurrentSpan());
+ * }</pre>
+ *
+ * @param spanName The name of the returned Span.
+ * @return a {@code SpanBuilder} to create and start a new {@code Span}.
+ * @throws NullPointerException if {@code spanName} is {@code null}.
+ * @since 0.5
+ */
+ public final SpanBuilder spanBuilder(String spanName) {
+ return spanBuilderWithExplicitParent(spanName, CurrentSpanUtils.getCurrentSpan());
+ }
+
+ /**
+ * Returns a {@link SpanBuilder} to create and start a new child {@link Span} (or root if parent
+ * is {@code null} or has an invalid {@link SpanContext}), with parent being the designated {@code
+ * Span}.
+ *
+ * <p>See {@link SpanBuilder} for usage examples.
+ *
+ * <p>This <b>must</b> be used to create a {@code Span} when manual Context propagation is used OR
+ * when creating a root {@code Span} with a {@code null} parent.
+ *
+ * @param spanName The name of the returned Span.
+ * @param parent The parent of the returned Span. If {@code null} the {@code SpanBuilder} will
+ * build a root {@code Span}.
+ * @return a {@code SpanBuilder} to create and start a new {@code Span}.
+ * @throws NullPointerException if {@code spanName} is {@code null}.
+ * @since 0.5
+ */
+ public abstract SpanBuilder spanBuilderWithExplicitParent(String spanName, @Nullable Span parent);
+
+ /**
+ * Returns a {@link SpanBuilder} to create and start a new child {@link Span} (or root if parent
+ * is {@link SpanContext#INVALID} or {@code null}), with parent being the remote {@link Span}
+ * designated by the {@link SpanContext}.
+ *
+ * <p>See {@link SpanBuilder} for usage examples.
+ *
+ * <p>This <b>must</b> be used to create a {@code Span} when the parent is in a different process.
+ * This is only intended for use by RPC systems or similar.
+ *
+ * <p>If no {@link SpanContext} OR fail to parse the {@link SpanContext} on the server side, users
+ * must call this method with a {@code null} remote parent {@code SpanContext}.
+ *
+ * @param spanName The name of the returned Span.
+ * @param remoteParentSpanContext The remote parent of the returned Span.
+ * @return a {@code SpanBuilder} to create and start a new {@code Span}.
+ * @throws NullPointerException if {@code spanName} is {@code null}.
+ * @since 0.5
+ */
+ public abstract SpanBuilder spanBuilderWithRemoteParent(
+ String spanName, @Nullable SpanContext remoteParentSpanContext);
+
+ // No-Op implementation of the Tracer.
+ private static final class NoopTracer extends Tracer {
+
+ @Override
+ public SpanBuilder spanBuilderWithExplicitParent(String spanName, @Nullable Span parent) {
+ return NoopSpanBuilder.createWithParent(spanName, parent);
+ }
+
+ @Override
+ public SpanBuilder spanBuilderWithRemoteParent(
+ String spanName, @Nullable SpanContext remoteParentSpanContext) {
+ return NoopSpanBuilder.createWithRemoteParent(spanName, remoteParentSpanContext);
+ }
+
+ private NoopTracer() {}
+ }
+
+ protected Tracer() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/Tracestate.java b/api/src/main/java/io/opencensus/trace/Tracestate.java
new file mode 100644
index 00000000..dae587c8
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Tracestate.java
@@ -0,0 +1,273 @@
+/*
+ * 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.trace;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.internal.Utils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Carries tracing-system specific context in a list of key-value pairs. TraceState allows different
+ * vendors propagate additional information and inter-operate with their legacy Id formats.
+ *
+ * <p>Implementation is optimized for a small list of key-value pairs.
+ *
+ * <p>Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter,
+ * and can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and
+ * forward slashes /.
+ *
+ * <p>Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the
+ * range 0x20 to 0x7E) except comma , and =.
+ *
+ * @since 0.16
+ */
+@Immutable
+@AutoValue
+@ExperimentalApi
+public abstract class Tracestate {
+ private static final int KEY_MAX_SIZE = 256;
+ private static final int VALUE_MAX_SIZE = 256;
+ private static final int MAX_KEY_VALUE_PAIRS = 32;
+
+ /**
+ * Returns the value to which the specified key is mapped, or null if this map contains no mapping
+ * for the key.
+ *
+ * @param key with which the specified value is to be associated
+ * @return the value to which the specified key is mapped, or null if this map contains no mapping
+ * for the key.
+ * @since 0.16
+ */
+ @javax.annotation.Nullable
+ public String get(String key) {
+ for (Entry entry : getEntries()) {
+ if (entry.getKey().equals(key)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a {@link List} view of the mappings contained in this {@code TraceState}.
+ *
+ * @return a {@link List} view of the mappings contained in this {@code TraceState}.
+ * @since 0.16
+ */
+ public abstract List<Entry> getEntries();
+
+ /**
+ * Returns a {@code Builder} based on an empty {@code Tracestate}.
+ *
+ * @return a {@code Builder} based on an empty {@code Tracestate}.
+ * @since 0.16
+ */
+ public static Builder builder() {
+ return new Builder(Builder.EMPTY);
+ }
+
+ /**
+ * Returns a {@code Builder} based on this {@code Tracestate}.
+ *
+ * @return a {@code Builder} based on this {@code Tracestate}.
+ * @since 0.16
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Builder class for {@link MessageEvent}.
+ *
+ * @since 0.16
+ */
+ @ExperimentalApi
+ public static final class Builder {
+ private final Tracestate parent;
+ @javax.annotation.Nullable private ArrayList<Entry> entries;
+
+ // Needs to be in this class to avoid initialization deadlock because super class depends on
+ // subclass (the auto-value generate class).
+ private static final Tracestate EMPTY = create(Collections.<Entry>emptyList());
+
+ private Builder(Tracestate parent) {
+ Utils.checkNotNull(parent, "parent");
+ this.parent = parent;
+ this.entries = null;
+ }
+
+ /**
+ * Adds or updates the {@code Entry} that has the given {@code key} if it is present. The new
+ * {@code Entry} will always be added in the front of the list of entries.
+ *
+ * @param key the key for the {@code Entry} to be added.
+ * @param value the value for the {@code Entry} to be added.
+ * @return this.
+ * @since 0.16
+ */
+ @SuppressWarnings("nullness")
+ public Builder set(String key, String value) {
+ // Initially create the Entry to validate input.
+ Entry entry = Entry.create(key, value);
+ if (entries == null) {
+ // Copy entries from the parent.
+ entries = new ArrayList<Entry>(parent.getEntries());
+ }
+ for (int i = 0; i < entries.size(); i++) {
+ if (entries.get(i).getKey().equals(entry.getKey())) {
+ entries.remove(i);
+ // Exit now because the entries list cannot contain duplicates.
+ break;
+ }
+ }
+ // Inserts the element at the front of this list.
+ entries.add(0, entry);
+ return this;
+ }
+
+ /**
+ * Removes the {@code Entry} that has the given {@code key} if it is present.
+ *
+ * @param key the key for the {@code Entry} to be removed.
+ * @return this.
+ * @since 0.16
+ */
+ @SuppressWarnings("nullness")
+ public Builder remove(String key) {
+ Utils.checkNotNull(key, "key");
+ if (entries == null) {
+ // Copy entries from the parent.
+ entries = new ArrayList<Entry>(parent.getEntries());
+ }
+ for (int i = 0; i < entries.size(); i++) {
+ if (entries.get(i).getKey().equals(key)) {
+ entries.remove(i);
+ // Exit now because the entries list cannot contain duplicates.
+ break;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Builds a TraceState by adding the entries to the parent in front of the key-value pairs list
+ * and removing duplicate entries.
+ *
+ * @return a TraceState with the new entries.
+ * @since 0.16
+ */
+ public Tracestate build() {
+ if (entries == null) {
+ return parent;
+ }
+ return Tracestate.create(entries);
+ }
+ }
+
+ /**
+ * Immutable key-value pair for {@code Tracestate}.
+ *
+ * @since 0.16
+ */
+ @Immutable
+ @AutoValue
+ @ExperimentalApi
+ public abstract static class Entry {
+ /**
+ * Creates a new {@code Entry} for the {@code Tracestate}.
+ *
+ * @param key the Entry's key.
+ * @param value the Entry's value.
+ * @since 0.16
+ */
+ public static Entry create(String key, String value) {
+ Utils.checkNotNull(key, "key");
+ Utils.checkNotNull(value, "value");
+ Utils.checkArgument(validateKey(key), "Invalid key %s", key);
+ Utils.checkArgument(validateValue(value), "Invalid value %s", value);
+ return new AutoValue_Tracestate_Entry(key, value);
+ }
+
+ /**
+ * Returns the key {@code String}.
+ *
+ * @return the key {@code String}.
+ * @since 0.16
+ */
+ public abstract String getKey();
+
+ /**
+ * Returns the value {@code String}.
+ *
+ * @return the value {@code String}.
+ * @since 0.16
+ */
+ public abstract String getValue();
+
+ Entry() {}
+ }
+
+ // Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter, and
+ // can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and
+ // forward slashes /.
+ private static boolean validateKey(String key) {
+ if (key.length() > KEY_MAX_SIZE
+ || key.isEmpty()
+ || key.charAt(0) < 'a'
+ || key.charAt(0) > 'z') {
+ return false;
+ }
+ for (int i = 1; i < key.length(); i++) {
+ char c = key.charAt(i);
+ if (!(c >= 'a' && c <= 'z')
+ && !(c >= '0' && c <= '9')
+ && c != '_'
+ && c != '-'
+ && c != '*'
+ && c != '/') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the range
+ // 0x20 to 0x7E) except comma , and =.
+ private static boolean validateValue(String value) {
+ if (value.length() > VALUE_MAX_SIZE || value.charAt(value.length() - 1) == ' ' /* '\u0020' */) {
+ return false;
+ }
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ if (c == ',' || c == '=' || c < ' ' /* '\u0020' */ || c > '~' /* '\u007E' */) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static Tracestate create(List<Entry> entries) {
+ Utils.checkState(entries.size() <= MAX_KEY_VALUE_PAIRS, "Invalid size");
+ return new AutoValue_Tracestate(Collections.unmodifiableList(entries));
+ }
+
+ Tracestate() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/Tracing.java b/api/src/main/java/io/opencensus/trace/Tracing.java
new file mode 100644
index 00000000..f55cd775
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/Tracing.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import io.opencensus.common.Clock;
+import io.opencensus.internal.DefaultVisibilityForTesting;
+import io.opencensus.internal.Provider;
+import io.opencensus.trace.config.TraceConfig;
+import io.opencensus.trace.export.ExportComponent;
+import io.opencensus.trace.propagation.PropagationComponent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Class that manages a global instance of the {@link TraceComponent}.
+ *
+ * @since 0.5
+ */
+public final class Tracing {
+ private static final Logger logger = Logger.getLogger(Tracing.class.getName());
+ private static final TraceComponent traceComponent =
+ loadTraceComponent(TraceComponent.class.getClassLoader());
+
+ /**
+ * Returns the global {@link Tracer}.
+ *
+ * @return the global {@code Tracer}.
+ * @since 0.5
+ */
+ public static Tracer getTracer() {
+ return traceComponent.getTracer();
+ }
+
+ /**
+ * Returns the global {@link PropagationComponent}.
+ *
+ * @return the global {@code PropagationComponent}.
+ * @since 0.5
+ */
+ public static PropagationComponent getPropagationComponent() {
+ return traceComponent.getPropagationComponent();
+ }
+
+ /**
+ * Returns the global {@link Clock}.
+ *
+ * @return the global {@code Clock}.
+ * @since 0.5
+ */
+ public static Clock getClock() {
+ return traceComponent.getClock();
+ }
+
+ /**
+ * Returns the global {@link ExportComponent}.
+ *
+ * @return the global {@code ExportComponent}.
+ * @since 0.5
+ */
+ public static ExportComponent getExportComponent() {
+ return traceComponent.getExportComponent();
+ }
+
+ /**
+ * Returns the global {@link TraceConfig}.
+ *
+ * @return the global {@code TraceConfig}.
+ * @since 0.5
+ */
+ public static TraceConfig getTraceConfig() {
+ return traceComponent.getTraceConfig();
+ }
+
+ // Any provider that may be used for TraceComponent can be added here.
+ @DefaultVisibilityForTesting
+ static TraceComponent loadTraceComponent(@Nullable ClassLoader classLoader) {
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impl.trace.TraceComponentImpl", /*initialize=*/ true, classLoader),
+ TraceComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load full implementation for TraceComponent, now trying to load lite "
+ + "implementation.",
+ e);
+ }
+ try {
+ // Call Class.forName with literal string name of the class to help shading tools.
+ return Provider.createInstance(
+ Class.forName(
+ "io.opencensus.impllite.trace.TraceComponentImplLite",
+ /*initialize=*/ true,
+ classLoader),
+ TraceComponent.class);
+ } catch (ClassNotFoundException e) {
+ logger.log(
+ Level.FINE,
+ "Couldn't load lite implementation for TraceComponent, now using "
+ + "default implementation for TraceComponent.",
+ e);
+ }
+ return TraceComponent.newNoopTraceComponent();
+ }
+
+ // No instance of this class.
+ private Tracing() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/config/TraceConfig.java b/api/src/main/java/io/opencensus/trace/config/TraceConfig.java
new file mode 100644
index 00000000..ff701e20
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/config/TraceConfig.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017, 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.trace.config;
+
+/**
+ * Global configuration of the trace service. This allows users to change configs for the default
+ * sampler, maximum events to be kept, etc. (see {@link TraceParams} for details).
+ *
+ * @since 0.5
+ */
+public abstract class TraceConfig {
+ private static final NoopTraceConfig NOOP_TRACE_CONFIG = new NoopTraceConfig();
+
+ /**
+ * Returns the active {@code TraceParams}.
+ *
+ * @return the active {@code TraceParams}.
+ * @since 0.5
+ */
+ public abstract TraceParams getActiveTraceParams();
+
+ /**
+ * Updates the active {@link TraceParams}.
+ *
+ * @param traceParams the new active {@code TraceParams}.
+ * @since 0.5
+ */
+ public abstract void updateActiveTraceParams(TraceParams traceParams);
+
+ /**
+ * Returns the no-op implementation of the {@code TraceConfig}.
+ *
+ * @return the no-op implementation of the {@code TraceConfig}.
+ * @since 0.5
+ */
+ public static TraceConfig getNoopTraceConfig() {
+ return NOOP_TRACE_CONFIG;
+ }
+
+ private static final class NoopTraceConfig extends TraceConfig {
+
+ @Override
+ public TraceParams getActiveTraceParams() {
+ return TraceParams.DEFAULT;
+ }
+
+ @Override
+ public void updateActiveTraceParams(TraceParams traceParams) {}
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/config/TraceParams.java b/api/src/main/java/io/opencensus/trace/config/TraceParams.java
new file mode 100644
index 00000000..ff70f52d
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/config/TraceParams.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2017, 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.trace.config;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.samplers.Samplers;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Class that holds global trace parameters.
+ *
+ * @since 0.5
+ */
+@AutoValue
+@Immutable
+public abstract class TraceParams {
+ // These values are the default values for all the global parameters.
+ private static final double DEFAULT_PROBABILITY = 1e-4;
+ private static final Sampler DEFAULT_SAMPLER = Samplers.probabilitySampler(DEFAULT_PROBABILITY);
+ private static final int DEFAULT_SPAN_MAX_NUM_ATTRIBUTES = 32;
+ private static final int DEFAULT_SPAN_MAX_NUM_ANNOTATIONS = 32;
+ private static final int DEFAULT_SPAN_MAX_NUM_MESSAGE_EVENTS = 128;
+ private static final int DEFAULT_SPAN_MAX_NUM_LINKS = 32;
+
+ /**
+ * Default {@code TraceParams}.
+ *
+ * @since 0.5
+ */
+ public static final TraceParams DEFAULT =
+ TraceParams.builder()
+ .setSampler(DEFAULT_SAMPLER)
+ .setMaxNumberOfAttributes(DEFAULT_SPAN_MAX_NUM_ATTRIBUTES)
+ .setMaxNumberOfAnnotations(DEFAULT_SPAN_MAX_NUM_ANNOTATIONS)
+ .setMaxNumberOfMessageEvents(DEFAULT_SPAN_MAX_NUM_MESSAGE_EVENTS)
+ .setMaxNumberOfLinks(DEFAULT_SPAN_MAX_NUM_LINKS)
+ .build();
+
+ /**
+ * Returns the global default {@code Sampler}. Used if no {@code Sampler} is provided in {@link
+ * io.opencensus.trace.SpanBuilder#setSampler(Sampler)}.
+ *
+ * @return the global default {@code Sampler}.
+ * @since 0.5
+ */
+ public abstract Sampler getSampler();
+
+ /**
+ * Returns the global default max number of attributes per {@link Span}.
+ *
+ * @return the global default max number of attributes per {@link Span}.
+ * @since 0.5
+ */
+ public abstract int getMaxNumberOfAttributes();
+
+ /**
+ * Returns the global default max number of {@link Annotation} events per {@link Span}.
+ *
+ * @return the global default max number of {@code Annotation} events per {@code Span}.
+ * @since 0.5
+ */
+ public abstract int getMaxNumberOfAnnotations();
+
+ /**
+ * Returns the global default max number of {@link MessageEvent} events per {@link Span}.
+ *
+ * @return the global default max number of {@code MessageEvent} events per {@code Span}.
+ * @since 0.12
+ */
+ public abstract int getMaxNumberOfMessageEvents();
+
+ /**
+ * Returns the global default max number of {@link io.opencensus.trace.NetworkEvent} events per
+ * {@link Span}.
+ *
+ * @return the global default max number of {@code NetworkEvent} events per {@code Span}.
+ * @deprecated Use {@link getMaxNumberOfMessageEvents}.
+ * @since 0.5
+ */
+ @Deprecated
+ public int getMaxNumberOfNetworkEvents() {
+ return getMaxNumberOfMessageEvents();
+ }
+
+ /**
+ * Returns the global default max number of {@link Link} entries per {@link Span}.
+ *
+ * @return the global default max number of {@code Link} entries per {@code Span}.
+ * @since 0.5
+ */
+ public abstract int getMaxNumberOfLinks();
+
+ private static Builder builder() {
+ return new AutoValue_TraceParams.Builder();
+ }
+
+ /**
+ * Returns a {@link Builder} initialized to the same property values as the current instance.
+ *
+ * @return a {@link Builder} initialized to the same property values as the current instance.
+ * @since 0.5
+ */
+ public abstract Builder toBuilder();
+
+ /**
+ * A {@code Builder} class for {@link TraceParams}.
+ *
+ * @since 0.5
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ /**
+ * Sets the global default {@code Sampler}. It must be not {@code null} otherwise {@link
+ * #build()} will throw an exception.
+ *
+ * @param sampler the global default {@code Sampler}.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract Builder setSampler(Sampler sampler);
+
+ /**
+ * Sets the global default max number of attributes per {@link Span}.
+ *
+ * @param maxNumberOfAttributes the global default max number of attributes per {@link Span}. It
+ * must be positive otherwise {@link #build()} will throw an exception.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract Builder setMaxNumberOfAttributes(int maxNumberOfAttributes);
+
+ /**
+ * Sets the global default max number of {@link Annotation} events per {@link Span}.
+ *
+ * @param maxNumberOfAnnotations the global default max number of {@link Annotation} events per
+ * {@link Span}. It must be positive otherwise {@link #build()} will throw an exception.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract Builder setMaxNumberOfAnnotations(int maxNumberOfAnnotations);
+
+ /**
+ * Sets the global default max number of {@link MessageEvent} events per {@link Span}.
+ *
+ * @param maxNumberOfMessageEvents the global default max number of {@link MessageEvent} events
+ * per {@link Span}. It must be positive otherwise {@link #build()} will throw an exception.
+ * @since 0.12
+ * @return this.
+ */
+ public abstract Builder setMaxNumberOfMessageEvents(int maxNumberOfMessageEvents);
+
+ /**
+ * Sets the global default max number of {@link io.opencensus.trace.NetworkEvent} events per
+ * {@link Span}.
+ *
+ * @param maxNumberOfNetworkEvents the global default max number of {@link
+ * io.opencensus.trace.NetworkEvent} events per {@link Span}. It must be positive otherwise
+ * {@link #build()} will throw an exception.
+ * @return this.
+ * @deprecated Use {@link setMaxNumberOfMessageEvents}.
+ * @since 0.5
+ */
+ @Deprecated
+ public Builder setMaxNumberOfNetworkEvents(int maxNumberOfNetworkEvents) {
+ return setMaxNumberOfMessageEvents(maxNumberOfNetworkEvents);
+ }
+
+ /**
+ * Sets the global default max number of {@link Link} entries per {@link Span}.
+ *
+ * @param maxNumberOfLinks the global default max number of {@link Link} entries per {@link
+ * Span}. It must be positive otherwise {@link #build()} will throw an exception.
+ * @return this.
+ * @since 0.5
+ */
+ public abstract Builder setMaxNumberOfLinks(int maxNumberOfLinks);
+
+ abstract TraceParams autoBuild();
+
+ /**
+ * Builds and returns a {@code TraceParams} with the desired values.
+ *
+ * @return a {@code TraceParams} with the desired values.
+ * @throws NullPointerException if the sampler is {@code null}.
+ * @throws IllegalArgumentException if any of the max numbers are not positive.
+ * @since 0.5
+ */
+ public TraceParams build() {
+ TraceParams traceParams = autoBuild();
+ Utils.checkArgument(traceParams.getMaxNumberOfAttributes() > 0, "maxNumberOfAttributes");
+ Utils.checkArgument(traceParams.getMaxNumberOfAnnotations() > 0, "maxNumberOfAnnotations");
+ Utils.checkArgument(
+ traceParams.getMaxNumberOfMessageEvents() > 0, "maxNumberOfMessageEvents");
+ Utils.checkArgument(traceParams.getMaxNumberOfLinks() > 0, "maxNumberOfLinks");
+ return traceParams;
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/export/ExportComponent.java b/api/src/main/java/io/opencensus/trace/export/ExportComponent.java
new file mode 100644
index 00000000..c334c5a6
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/export/ExportComponent.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import io.opencensus.trace.TraceOptions;
+
+/**
+ * Class that holds the implementation instances for {@link SpanExporter}, {@link RunningSpanStore}
+ * and {@link SampledSpanStore}.
+ *
+ * <p>Unless otherwise noted all methods (on component) results are cacheable.
+ *
+ * @since 0.5
+ */
+public abstract class ExportComponent {
+
+ /**
+ * Returns the no-op implementation of the {@code ExportComponent}.
+ *
+ * @return the no-op implementation of the {@code ExportComponent}.
+ * @since 0.5
+ */
+ public static ExportComponent newNoopExportComponent() {
+ return new NoopExportComponent();
+ }
+
+ /**
+ * Returns the {@link SpanExporter} which can be used to register handlers to export all the spans
+ * that are part of a distributed sampled trace (see {@link TraceOptions#isSampled()}).
+ *
+ * @return the implementation of the {@code SpanExporter} or no-op if no implementation linked in
+ * the binary.
+ * @since 0.5
+ */
+ public abstract SpanExporter getSpanExporter();
+
+ /**
+ * Returns the {@link RunningSpanStore} that can be used to get useful debugging information about
+ * all the current active spans.
+ *
+ * @return the {@code RunningSpanStore}.
+ * @since 0.5
+ */
+ public abstract RunningSpanStore getRunningSpanStore();
+
+ /**
+ * Returns the {@link SampledSpanStore} that can be used to get useful debugging information, such
+ * as latency based sampled spans, error based sampled spans.
+ *
+ * @return the {@code SampledSpanStore}.
+ * @since 0.5
+ */
+ public abstract SampledSpanStore getSampledSpanStore();
+
+ /**
+ * Will shutdown this ExportComponent after flushing any pending spans.
+ *
+ * @since 0.14
+ */
+ public void shutdown() {}
+
+ private static final class NoopExportComponent extends ExportComponent {
+ private final SampledSpanStore noopSampledSpanStore =
+ SampledSpanStore.newNoopSampledSpanStore();
+
+ @Override
+ public SpanExporter getSpanExporter() {
+ return SpanExporter.getNoopSpanExporter();
+ }
+
+ @Override
+ public RunningSpanStore getRunningSpanStore() {
+ return RunningSpanStore.getNoopRunningSpanStore();
+ }
+
+ @Override
+ public SampledSpanStore getSampledSpanStore() {
+ return noopSampledSpanStore;
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/export/RunningSpanStore.java b/api/src/main/java/io/opencensus/trace/export/RunningSpanStore.java
new file mode 100644
index 00000000..fac3c855
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/export/RunningSpanStore.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.Utils;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * This class allows users to access in-process information about all running spans.
+ *
+ * <p>The running spans tracking is available for all the spans with the option {@link
+ * io.opencensus.trace.Span.Options#RECORD_EVENTS}. This functionality allows users to debug stuck
+ * operations or long living operations.
+ *
+ * @since 0.5
+ */
+@ThreadSafe
+public abstract class RunningSpanStore {
+
+ private static final RunningSpanStore NOOP_RUNNING_SPAN_STORE = new NoopRunningSpanStore();
+
+ protected RunningSpanStore() {}
+
+ /**
+ * Returns the no-op implementation of the {@code RunningSpanStore}.
+ *
+ * @return the no-op implementation of the {@code RunningSpanStore}.
+ */
+ static RunningSpanStore getNoopRunningSpanStore() {
+ return NOOP_RUNNING_SPAN_STORE;
+ }
+
+ /**
+ * Returns the summary of all available data such, as number of running spans.
+ *
+ * @return the summary of all available data.
+ * @since 0.5
+ */
+ public abstract Summary getSummary();
+
+ /**
+ * Returns a list of running spans that match the {@code Filter}.
+ *
+ * @param filter used to filter the returned spans.
+ * @return a list of running spans that match the {@code Filter}.
+ * @since 0.5
+ */
+ public abstract Collection<SpanData> getRunningSpans(Filter filter);
+
+ /**
+ * The summary of all available data.
+ *
+ * @since 0.5
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class Summary {
+
+ Summary() {}
+
+ /**
+ * Returns a new instance of {@code Summary}.
+ *
+ * @param perSpanNameSummary a map with summary for each span name.
+ * @return a new instance of {@code Summary}.
+ * @throws NullPointerException if {@code perSpanNameSummary} is {@code null}.
+ * @since 0.5
+ */
+ public static Summary create(Map<String, PerSpanNameSummary> perSpanNameSummary) {
+ return new AutoValue_RunningSpanStore_Summary(
+ Collections.unmodifiableMap(
+ new HashMap<String, PerSpanNameSummary>(
+ Utils.checkNotNull(perSpanNameSummary, "perSpanNameSummary"))));
+ }
+
+ /**
+ * Returns a map with summary of available data for each span name.
+ *
+ * @return a map with all the span names and the summary.
+ * @since 0.5
+ */
+ public abstract Map<String, PerSpanNameSummary> getPerSpanNameSummary();
+ }
+
+ /**
+ * Summary of all available data for a span name.
+ *
+ * @since 0.5
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class PerSpanNameSummary {
+
+ PerSpanNameSummary() {}
+
+ /**
+ * Returns a new instance of {@code PerSpanNameSummary}.
+ *
+ * @param numRunningSpans the number of running spans.
+ * @return a new instance of {@code PerSpanNameSummary}.
+ * @throws IllegalArgumentException if {@code numRunningSpans} is negative.
+ * @since 0.5
+ */
+ public static PerSpanNameSummary create(int numRunningSpans) {
+ Utils.checkArgument(numRunningSpans >= 0, "Negative numRunningSpans.");
+ return new AutoValue_RunningSpanStore_PerSpanNameSummary(numRunningSpans);
+ }
+
+ /**
+ * Returns the number of running spans.
+ *
+ * @return the number of running spans.
+ * @since 0.5
+ */
+ public abstract int getNumRunningSpans();
+ }
+
+ /**
+ * Filter for running spans. Used to filter results returned by the {@link
+ * #getRunningSpans(Filter)} request.
+ *
+ * @since 0.5
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class Filter {
+
+ Filter() {}
+
+ /**
+ * Returns a new instance of {@code Filter}.
+ *
+ * <p>Filters all the spans based on {@code spanName} and returns a maximum of {@code
+ * maxSpansToReturn}.
+ *
+ * @param spanName the name of the span.
+ * @param maxSpansToReturn the maximum number of results to be returned. {@code 0} means all.
+ * @return a new instance of {@code Filter}.
+ * @throws NullPointerException if {@code spanName} is {@code null}.
+ * @throws IllegalArgumentException if {@code maxSpansToReturn} is negative.
+ * @since 0.5
+ */
+ public static Filter create(String spanName, int maxSpansToReturn) {
+ Utils.checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn.");
+ return new AutoValue_RunningSpanStore_Filter(spanName, maxSpansToReturn);
+ }
+
+ /**
+ * Returns the span name.
+ *
+ * @return the span name.
+ * @since 0.5
+ */
+ public abstract String getSpanName();
+
+ /**
+ * Returns the maximum number of spans to be returned. {@code 0} means all.
+ *
+ * @return the maximum number of spans to be returned.
+ * @since 0.5
+ */
+ public abstract int getMaxSpansToReturn();
+ }
+
+ private static final class NoopRunningSpanStore extends RunningSpanStore {
+
+ private static final Summary EMPTY_SUMMARY =
+ Summary.create(Collections.<String, PerSpanNameSummary>emptyMap());
+
+ @Override
+ public Summary getSummary() {
+ return EMPTY_SUMMARY;
+ }
+
+ @Override
+ public Collection<SpanData> getRunningSpans(Filter filter) {
+ Utils.checkNotNull(filter, "filter");
+ return Collections.<SpanData>emptyList();
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/export/SampledSpanStore.java b/api/src/main/java/io/opencensus/trace/export/SampledSpanStore.java
new file mode 100644
index 00000000..5d00a45d
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/export/SampledSpanStore.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.Status;
+import io.opencensus.trace.Status.CanonicalCode;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * This class allows users to access in-process information such as latency based sampled spans and
+ * error based sampled spans.
+ *
+ * <p>For all completed spans with the option {@link Span.Options#RECORD_EVENTS} the library can
+ * store samples based on latency for succeeded operations or based on error code for failed
+ * operations. To activate this, users MUST manually configure all the span names for which samples
+ * will be collected (see {@link #registerSpanNamesForCollection(Collection)}).
+ *
+ * @since 0.5
+ */
+@ThreadSafe
+public abstract class SampledSpanStore {
+
+ protected SampledSpanStore() {}
+
+ /**
+ * Returns a {@code SampledSpanStore} that maintains a set of span names, but always returns an
+ * empty list of {@link SpanData}.
+ *
+ * @return a {@code SampledSpanStore} that maintains a set of span names, but always returns an
+ * empty list of {@code SpanData}.
+ */
+ static SampledSpanStore newNoopSampledSpanStore() {
+ return new NoopSampledSpanStore();
+ }
+
+ /**
+ * Returns the summary of all available data, such as number of sampled spans in the latency based
+ * samples or error based samples.
+ *
+ * <p>Data available only for span names registered using {@link
+ * #registerSpanNamesForCollection(Collection)}.
+ *
+ * @return the summary of all available data.
+ * @since 0.5
+ */
+ public abstract Summary getSummary();
+
+ /**
+ * Returns a list of succeeded spans (spans with {@link Status} equal to {@link Status#OK}) that
+ * match the {@code filter}.
+ *
+ * <p>Latency based sampled spans are available only for span names registered using {@link
+ * #registerSpanNamesForCollection(Collection)}.
+ *
+ * @param filter used to filter the returned sampled spans.
+ * @return a list of succeeded spans that match the {@code filter}.
+ * @since 0.5
+ */
+ public abstract Collection<SpanData> getLatencySampledSpans(LatencyFilter filter);
+
+ /**
+ * Returns a list of failed spans (spans with {@link Status} other than {@link Status#OK}) that
+ * match the {@code filter}.
+ *
+ * <p>Error based sampled spans are available only for span names registered using {@link
+ * #registerSpanNamesForCollection(Collection)}.
+ *
+ * @param filter used to filter the returned sampled spans.
+ * @return a list of failed spans that match the {@code filter}.
+ * @since 0.5
+ */
+ public abstract Collection<SpanData> getErrorSampledSpans(ErrorFilter filter);
+
+ /**
+ * Appends a list of span names for which the library will collect latency based sampled spans and
+ * error based sampled spans.
+ *
+ * <p>If called multiple times the library keeps the list of unique span names from all the calls.
+ *
+ * @param spanNames list of span names for which the library will collect samples.
+ * @since 0.5
+ */
+ public abstract void registerSpanNamesForCollection(Collection<String> spanNames);
+
+ /**
+ * Removes a list of span names for which the library will collect latency based sampled spans and
+ * error based sampled spans.
+ *
+ * <p>The library keeps the list of unique registered span names for which samples will be called.
+ * This method allows users to remove span names from that list.
+ *
+ * @param spanNames list of span names for which the library will no longer collect samples.
+ * @since 0.5
+ */
+ public abstract void unregisterSpanNamesForCollection(Collection<String> spanNames);
+
+ /**
+ * Returns the set of unique span names registered to the library, for use in tests. For this set
+ * of span names the library will collect latency based sampled spans and error based sampled
+ * spans.
+ *
+ * <p>This method is only meant for testing code that uses OpenCensus, and it is not performant.
+ *
+ * @return the set of unique span names registered to the library.
+ * @since 0.7
+ */
+ public abstract Set<String> getRegisteredSpanNamesForCollection();
+
+ /**
+ * The summary of all available data.
+ *
+ * @since 0.5
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class Summary {
+
+ Summary() {}
+
+ /**
+ * Returns a new instance of {@code Summary}.
+ *
+ * @param perSpanNameSummary a map with summary for each span name.
+ * @return a new instance of {@code Summary}.
+ * @throws NullPointerException if {@code perSpanNameSummary} is {@code null}.
+ * @since 0.5
+ */
+ public static Summary create(Map<String, PerSpanNameSummary> perSpanNameSummary) {
+ return new AutoValue_SampledSpanStore_Summary(
+ Collections.unmodifiableMap(
+ new HashMap<String, PerSpanNameSummary>(
+ Utils.checkNotNull(perSpanNameSummary, "perSpanNameSummary"))));
+ }
+
+ /**
+ * Returns a map with summary of available data for each span name.
+ *
+ * @return a map with all the span names and the summary.
+ * @since 0.5
+ */
+ public abstract Map<String, PerSpanNameSummary> getPerSpanNameSummary();
+ }
+
+ /**
+ * Summary of all available data for a span name.
+ *
+ * @since 0.5
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class PerSpanNameSummary {
+
+ PerSpanNameSummary() {}
+
+ /**
+ * Returns a new instance of {@code PerSpanNameSummary}.
+ *
+ * @param numbersOfLatencySampledSpans the summary for the latency buckets.
+ * @param numbersOfErrorSampledSpans the summary for the error buckets.
+ * @return a new instance of {@code PerSpanNameSummary}.
+ * @throws NullPointerException if {@code numbersOfLatencySampledSpans} or {@code
+ * numbersOfErrorSampledSpans} are {@code null}.
+ * @since 0.5
+ */
+ public static PerSpanNameSummary create(
+ Map<LatencyBucketBoundaries, Integer> numbersOfLatencySampledSpans,
+ Map<CanonicalCode, Integer> numbersOfErrorSampledSpans) {
+ return new AutoValue_SampledSpanStore_PerSpanNameSummary(
+ Collections.unmodifiableMap(
+ new HashMap<LatencyBucketBoundaries, Integer>(
+ Utils.checkNotNull(
+ numbersOfLatencySampledSpans, "numbersOfLatencySampledSpans"))),
+ Collections.unmodifiableMap(
+ new HashMap<CanonicalCode, Integer>(
+ Utils.checkNotNull(numbersOfErrorSampledSpans, "numbersOfErrorSampledSpans"))));
+ }
+
+ /**
+ * Returns the number of sampled spans in all the latency buckets.
+ *
+ * <p>Data available only for span names registered using {@link
+ * #registerSpanNamesForCollection(Collection)}.
+ *
+ * @return the number of sampled spans in all the latency buckets.
+ * @since 0.5
+ */
+ public abstract Map<LatencyBucketBoundaries, Integer> getNumbersOfLatencySampledSpans();
+
+ /**
+ * Returns the number of sampled spans in all the error buckets.
+ *
+ * <p>Data available only for span names registered using {@link
+ * #registerSpanNamesForCollection(Collection)}.
+ *
+ * @return the number of sampled spans in all the error buckets.
+ * @since 0.5
+ */
+ public abstract Map<CanonicalCode, Integer> getNumbersOfErrorSampledSpans();
+ }
+
+ /**
+ * The latency buckets boundaries. Samples based on latency for successful spans (the status of
+ * the span has a canonical code equal to {@link CanonicalCode#OK}) are collected in one of these
+ * latency buckets.
+ *
+ * @since 0.5
+ */
+ public enum LatencyBucketBoundaries {
+ /**
+ * Stores finished successful requests of duration within the interval [0, 10us).
+ *
+ * @since 0.5
+ */
+ ZERO_MICROSx10(0, TimeUnit.MICROSECONDS.toNanos(10)),
+
+ /**
+ * Stores finished successful requests of duration within the interval [10us, 100us).
+ *
+ * @since 0.5
+ */
+ MICROSx10_MICROSx100(TimeUnit.MICROSECONDS.toNanos(10), TimeUnit.MICROSECONDS.toNanos(100)),
+
+ /**
+ * Stores finished successful requests of duration within the interval [100us, 1ms).
+ *
+ * @since 0.5
+ */
+ MICROSx100_MILLIx1(TimeUnit.MICROSECONDS.toNanos(100), TimeUnit.MILLISECONDS.toNanos(1)),
+
+ /**
+ * Stores finished successful requests of duration within the interval [1ms, 10ms).
+ *
+ * @since 0.5
+ */
+ MILLIx1_MILLIx10(TimeUnit.MILLISECONDS.toNanos(1), TimeUnit.MILLISECONDS.toNanos(10)),
+
+ /**
+ * Stores finished successful requests of duration within the interval [10ms, 100ms).
+ *
+ * @since 0.5
+ */
+ MILLIx10_MILLIx100(TimeUnit.MILLISECONDS.toNanos(10), TimeUnit.MILLISECONDS.toNanos(100)),
+
+ /**
+ * Stores finished successful requests of duration within the interval [100ms, 1sec).
+ *
+ * @since 0.5
+ */
+ MILLIx100_SECONDx1(TimeUnit.MILLISECONDS.toNanos(100), TimeUnit.SECONDS.toNanos(1)),
+
+ /**
+ * Stores finished successful requests of duration within the interval [1sec, 10sec).
+ *
+ * @since 0.5
+ */
+ SECONDx1_SECONDx10(TimeUnit.SECONDS.toNanos(1), TimeUnit.SECONDS.toNanos(10)),
+
+ /**
+ * Stores finished successful requests of duration within the interval [10sec, 100sec).
+ *
+ * @since 0.5
+ */
+ SECONDx10_SECONDx100(TimeUnit.SECONDS.toNanos(10), TimeUnit.SECONDS.toNanos(100)),
+
+ /**
+ * Stores finished successful requests of duration &gt;= 100sec.
+ *
+ * @since 0.5
+ */
+ SECONDx100_MAX(TimeUnit.SECONDS.toNanos(100), Long.MAX_VALUE);
+
+ /**
+ * Constructs a {@code LatencyBucketBoundaries} with the given boundaries and label.
+ *
+ * @param latencyLowerNs the latency lower bound of the bucket.
+ * @param latencyUpperNs the latency upper bound of the bucket.
+ */
+ LatencyBucketBoundaries(long latencyLowerNs, long latencyUpperNs) {
+ this.latencyLowerNs = latencyLowerNs;
+ this.latencyUpperNs = latencyUpperNs;
+ }
+
+ /**
+ * Returns the latency lower bound of the bucket.
+ *
+ * @return the latency lower bound of the bucket.
+ * @since 0.5
+ */
+ public long getLatencyLowerNs() {
+ return latencyLowerNs;
+ }
+
+ /**
+ * Returns the latency upper bound of the bucket.
+ *
+ * @return the latency upper bound of the bucket.
+ * @since 0.5
+ */
+ public long getLatencyUpperNs() {
+ return latencyUpperNs;
+ }
+
+ private final long latencyLowerNs;
+ private final long latencyUpperNs;
+ }
+
+ /**
+ * Filter for latency based sampled spans. Used to filter results returned by the {@link
+ * #getLatencySampledSpans(LatencyFilter)} request.
+ *
+ * @since 0.5
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class LatencyFilter {
+
+ LatencyFilter() {}
+
+ /**
+ * Returns a new instance of {@code LatencyFilter}.
+ *
+ * <p>Filters all the spans based on {@code spanName} and latency in the interval
+ * [latencyLowerNs, latencyUpperNs) and returns a maximum of {@code maxSpansToReturn}.
+ *
+ * @param spanName the name of the span.
+ * @param latencyLowerNs the latency lower bound.
+ * @param latencyUpperNs the latency upper bound.
+ * @param maxSpansToReturn the maximum number of results to be returned. {@code 0} means all.
+ * @return a new instance of {@code LatencyFilter}.
+ * @throws NullPointerException if {@code spanName} is {@code null}.
+ * @throws IllegalArgumentException if {@code maxSpansToReturn} or {@code latencyLowerNs} or
+ * {@code latencyUpperNs} are negative.
+ * @since 0.5
+ */
+ public static LatencyFilter create(
+ String spanName, long latencyLowerNs, long latencyUpperNs, int maxSpansToReturn) {
+ Utils.checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn.");
+ Utils.checkArgument(latencyLowerNs >= 0, "Negative latencyLowerNs");
+ Utils.checkArgument(latencyUpperNs >= 0, "Negative latencyUpperNs");
+ return new AutoValue_SampledSpanStore_LatencyFilter(
+ spanName, latencyLowerNs, latencyUpperNs, maxSpansToReturn);
+ }
+
+ /**
+ * Returns the span name used by this filter.
+ *
+ * @return the span name used by this filter.
+ * @since 0.5
+ */
+ public abstract String getSpanName();
+
+ /**
+ * Returns the latency lower bound of this bucket (inclusive).
+ *
+ * @return the latency lower bound of this bucket.
+ * @since 0.5
+ */
+ public abstract long getLatencyLowerNs();
+
+ /**
+ * Returns the latency upper bound of this bucket (exclusive).
+ *
+ * @return the latency upper bound of this bucket.
+ * @since 0.5
+ */
+ public abstract long getLatencyUpperNs();
+
+ /**
+ * Returns the maximum number of spans to be returned. {@code 0} means all.
+ *
+ * @return the maximum number of spans to be returned.
+ * @since 0.5
+ */
+ public abstract int getMaxSpansToReturn();
+ }
+
+ /**
+ * Filter for error based sampled spans. Used to filter results returned by the {@link
+ * #getErrorSampledSpans(ErrorFilter)} request.
+ *
+ * @since 0.5
+ */
+ @AutoValue
+ @Immutable
+ public abstract static class ErrorFilter {
+
+ ErrorFilter() {}
+
+ /**
+ * Returns a new instance of {@code ErrorFilter}.
+ *
+ * <p>Filters all the spans based on {@code spanName} and {@code canonicalCode} and returns a
+ * maximum of {@code maxSpansToReturn}.
+ *
+ * @param spanName the name of the span.
+ * @param canonicalCode the error code of the span. {@code null} can be used to query all error
+ * codes.
+ * @param maxSpansToReturn the maximum number of results to be returned. {@code 0} means all.
+ * @return a new instance of {@code ErrorFilter}.
+ * @throws NullPointerException if {@code spanName} is {@code null}.
+ * @throws IllegalArgumentException if {@code canonicalCode} is {@link CanonicalCode#OK} or
+ * {@code maxSpansToReturn} is negative.
+ * @since 0.5
+ */
+ public static ErrorFilter create(
+ String spanName, @Nullable CanonicalCode canonicalCode, int maxSpansToReturn) {
+ if (canonicalCode != null) {
+ Utils.checkArgument(canonicalCode != CanonicalCode.OK, "Invalid canonical code.");
+ }
+ Utils.checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn.");
+ return new AutoValue_SampledSpanStore_ErrorFilter(spanName, canonicalCode, maxSpansToReturn);
+ }
+
+ /**
+ * Returns the span name used by this filter.
+ *
+ * @return the span name used by this filter.
+ * @since 0.5
+ */
+ public abstract String getSpanName();
+
+ /**
+ * Returns the canonical code used by this filter. Always different than {@link
+ * CanonicalCode#OK}. If {@code null} then all errors match.
+ *
+ * @return the canonical code used by this filter.
+ * @since 0.5
+ */
+ @Nullable
+ public abstract CanonicalCode getCanonicalCode();
+
+ /**
+ * Returns the maximum number of spans to be returned. Used to enforce the number of returned
+ * {@code SpanData}. {@code 0} means all.
+ *
+ * @return the maximum number of spans to be returned.
+ * @since 0.5
+ */
+ public abstract int getMaxSpansToReturn();
+ }
+
+ @ThreadSafe
+ private static final class NoopSampledSpanStore extends SampledSpanStore {
+ private static final PerSpanNameSummary EMPTY_PER_SPAN_NAME_SUMMARY =
+ PerSpanNameSummary.create(
+ Collections.<SampledSpanStore.LatencyBucketBoundaries, Integer>emptyMap(),
+ Collections.<CanonicalCode, Integer>emptyMap());
+
+ @GuardedBy("registeredSpanNames")
+ private final Set<String> registeredSpanNames = new HashSet<String>();
+
+ @Override
+ public Summary getSummary() {
+ Map<String, PerSpanNameSummary> result = new HashMap<String, PerSpanNameSummary>();
+ synchronized (registeredSpanNames) {
+ for (String registeredSpanName : registeredSpanNames) {
+ result.put(registeredSpanName, EMPTY_PER_SPAN_NAME_SUMMARY);
+ }
+ }
+ return Summary.create(result);
+ }
+
+ @Override
+ public Collection<SpanData> getLatencySampledSpans(LatencyFilter filter) {
+ Utils.checkNotNull(filter, "latencyFilter");
+ return Collections.<SpanData>emptyList();
+ }
+
+ @Override
+ public Collection<SpanData> getErrorSampledSpans(ErrorFilter filter) {
+ Utils.checkNotNull(filter, "errorFilter");
+ return Collections.<SpanData>emptyList();
+ }
+
+ @Override
+ public void registerSpanNamesForCollection(Collection<String> spanNames) {
+ Utils.checkNotNull(spanNames, "spanNames");
+ synchronized (registeredSpanNames) {
+ registeredSpanNames.addAll(spanNames);
+ }
+ }
+
+ @Override
+ public void unregisterSpanNamesForCollection(Collection<String> spanNames) {
+ Utils.checkNotNull(spanNames, "spanNames");
+ synchronized (registeredSpanNames) {
+ registeredSpanNames.removeAll(spanNames);
+ }
+ }
+
+ @Override
+ public Set<String> getRegisteredSpanNamesForCollection() {
+ synchronized (registeredSpanNames) {
+ return Collections.<String>unmodifiableSet(new HashSet<String>(registeredSpanNames));
+ }
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/export/SpanData.java b/api/src/main/java/io/opencensus/trace/export/SpanData.java
new file mode 100644
index 00000000..f4dd4682
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/export/SpanData.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Timestamp;
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.Span;
+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.internal.BaseMessageEventUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/*>>>
+import org.checkerframework.dataflow.qual.Deterministic;
+*/
+
+/**
+ * Immutable representation of all data collected by the {@link Span} class.
+ *
+ * @since 0.5
+ */
+@Immutable
+@AutoValue
+public abstract class SpanData {
+
+ /**
+ * Returns a new immutable {@code SpanData}.
+ *
+ * @deprecated Use {@link #create(SpanContext, SpanId, Boolean, String, Kind, Timestamp,
+ * Attributes, TimedEvents, TimedEvents, Links, Integer, Status, Timestamp)}.
+ */
+ @Deprecated
+ public static SpanData create(
+ SpanContext context,
+ @Nullable SpanId parentSpanId,
+ @Nullable Boolean hasRemoteParent,
+ String name,
+ Timestamp startTimestamp,
+ Attributes attributes,
+ TimedEvents<Annotation> annotations,
+ TimedEvents<? extends io.opencensus.trace.BaseMessageEvent> messageOrNetworkEvents,
+ Links links,
+ @Nullable Integer childSpanCount,
+ @Nullable Status status,
+ @Nullable Timestamp endTimestamp) {
+ return create(
+ context,
+ parentSpanId,
+ hasRemoteParent,
+ name,
+ null,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageOrNetworkEvents,
+ links,
+ childSpanCount,
+ status,
+ endTimestamp);
+ }
+
+ /**
+ * Returns a new immutable {@code SpanData}.
+ *
+ * @param context the {@code SpanContext} of the {@code Span}.
+ * @param parentSpanId the parent {@code SpanId} of the {@code Span}. {@code null} if the {@code
+ * Span} is a root.
+ * @param hasRemoteParent {@code true} if the parent {@code Span} is remote. {@code null} if this
+ * is a root span.
+ * @param name the name of the {@code Span}.
+ * @param kind the kind of the {@code Span}.
+ * @param startTimestamp the start {@code Timestamp} of the {@code Span}.
+ * @param attributes the attributes associated with the {@code Span}.
+ * @param annotations the annotations associated with the {@code Span}.
+ * @param messageOrNetworkEvents the message events (or network events for backward compatibility)
+ * associated with the {@code Span}.
+ * @param links the links associated with the {@code Span}.
+ * @param childSpanCount the number of child spans that were generated while the span was active.
+ * @param status the {@code Status} of the {@code Span}. {@code null} if the {@code Span} is still
+ * active.
+ * @param endTimestamp the end {@code Timestamp} of the {@code Span}. {@code null} if the {@code
+ * Span} is still active.
+ * @return a new immutable {@code SpanData}.
+ * @since 0.14
+ */
+ @SuppressWarnings({"deprecation", "InconsistentOverloads"})
+ public static SpanData create(
+ SpanContext context,
+ @Nullable SpanId parentSpanId,
+ @Nullable Boolean hasRemoteParent,
+ String name,
+ @Nullable Kind kind,
+ Timestamp startTimestamp,
+ Attributes attributes,
+ TimedEvents<Annotation> annotations,
+ TimedEvents<? extends io.opencensus.trace.BaseMessageEvent> messageOrNetworkEvents,
+ Links links,
+ @Nullable Integer childSpanCount,
+ @Nullable Status status,
+ @Nullable Timestamp endTimestamp) {
+ Utils.checkNotNull(messageOrNetworkEvents, "messageOrNetworkEvents");
+ List<TimedEvent<MessageEvent>> messageEventsList = new ArrayList<TimedEvent<MessageEvent>>();
+ for (TimedEvent<? extends io.opencensus.trace.BaseMessageEvent> timedEvent :
+ messageOrNetworkEvents.getEvents()) {
+ io.opencensus.trace.BaseMessageEvent event = timedEvent.getEvent();
+ if (event instanceof MessageEvent) {
+ @SuppressWarnings("unchecked")
+ TimedEvent<MessageEvent> timedMessageEvent = (TimedEvent<MessageEvent>) timedEvent;
+ messageEventsList.add(timedMessageEvent);
+ } else {
+ messageEventsList.add(
+ TimedEvent.<MessageEvent>create(
+ timedEvent.getTimestamp(), BaseMessageEventUtils.asMessageEvent(event)));
+ }
+ }
+ TimedEvents<MessageEvent> messageEvents =
+ TimedEvents.<MessageEvent>create(
+ messageEventsList, messageOrNetworkEvents.getDroppedEventsCount());
+ return new AutoValue_SpanData(
+ context,
+ parentSpanId,
+ hasRemoteParent,
+ name,
+ kind,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ childSpanCount,
+ status,
+ endTimestamp);
+ }
+
+ /**
+ * Returns the {@code SpanContext} associated with this {@code Span}.
+ *
+ * @return the {@code SpanContext} associated with this {@code Span}.
+ * @since 0.5
+ */
+ public abstract SpanContext getContext();
+
+ /**
+ * Returns the parent {@code SpanId} or {@code null} if the {@code Span} is a root {@code Span}.
+ *
+ * @return the parent {@code SpanId} or {@code null} if the {@code Span} is a root {@code Span}.
+ * @since 0.5
+ */
+ @Nullable
+ /*@Deterministic*/
+ public abstract SpanId getParentSpanId();
+
+ /**
+ * Returns {@code true} if the parent is on a different process. {@code null} if this is a root
+ * span.
+ *
+ * @return {@code true} if the parent is on a different process. {@code null} if this is a root
+ * span.
+ * @since 0.5
+ */
+ @Nullable
+ public abstract Boolean getHasRemoteParent();
+
+ /**
+ * Returns the name of this {@code Span}.
+ *
+ * @return the name of this {@code Span}.
+ * @since 0.5
+ */
+ public abstract String getName();
+
+ /**
+ * Returns the kind of this {@code Span}.
+ *
+ * @return the kind of this {@code Span}.
+ * @since 0.14
+ */
+ @Nullable
+ public abstract Kind getKind();
+
+ /**
+ * Returns the start {@code Timestamp} of this {@code Span}.
+ *
+ * @return the start {@code Timestamp} of this {@code Span}.
+ * @since 0.5
+ */
+ public abstract Timestamp getStartTimestamp();
+
+ /**
+ * Returns the attributes recorded for this {@code Span}.
+ *
+ * @return the attributes recorded for this {@code Span}.
+ * @since 0.5
+ */
+ public abstract Attributes getAttributes();
+
+ /**
+ * Returns the annotations recorded for this {@code Span}.
+ *
+ * @return the annotations recorded for this {@code Span}.
+ * @since 0.5
+ */
+ public abstract TimedEvents<Annotation> getAnnotations();
+
+ /**
+ * Returns network events recorded for this {@code Span}.
+ *
+ * @return network events recorded for this {@code Span}.
+ * @deprecated Use {@link #getMessageEvents}.
+ * @since 0.5
+ */
+ @Deprecated
+ @SuppressWarnings({"deprecation"})
+ public TimedEvents<io.opencensus.trace.NetworkEvent> getNetworkEvents() {
+ TimedEvents<MessageEvent> timedEvents = getMessageEvents();
+ List<TimedEvent<io.opencensus.trace.NetworkEvent>> networkEventsList =
+ new ArrayList<TimedEvent<io.opencensus.trace.NetworkEvent>>();
+ for (TimedEvent<MessageEvent> timedEvent : timedEvents.getEvents()) {
+ networkEventsList.add(
+ TimedEvent.<io.opencensus.trace.NetworkEvent>create(
+ timedEvent.getTimestamp(),
+ BaseMessageEventUtils.asNetworkEvent(timedEvent.getEvent())));
+ }
+ return TimedEvents.<io.opencensus.trace.NetworkEvent>create(
+ networkEventsList, timedEvents.getDroppedEventsCount());
+ }
+
+ /**
+ * Returns message events recorded for this {@code Span}.
+ *
+ * @return message events recorded for this {@code Span}.
+ * @since 0.12
+ */
+ public abstract TimedEvents<MessageEvent> getMessageEvents();
+
+ /**
+ * Returns links recorded for this {@code Span}.
+ *
+ * @return links recorded for this {@code Span}.
+ * @since 0.5
+ */
+ public abstract Links getLinks();
+
+ /**
+ * Returns the number of child spans that were generated while the {@code Span} was running. If
+ * not {@code null} allows service implementations to detect missing child spans.
+ *
+ * <p>This information is not always available.
+ *
+ * @return the number of child spans that were generated while the {@code Span} was running.
+ * @since 0.5
+ */
+ @Nullable
+ public abstract Integer getChildSpanCount();
+
+ /**
+ * Returns the {@code Status} or {@code null} if {@code Span} is still active.
+ *
+ * @return the {@code Status} or {@code null} if {@code Span} is still active.
+ * @since 0.5
+ */
+ @Nullable
+ /*@Deterministic*/
+ public abstract Status getStatus();
+
+ /**
+ * Returns the end {@code Timestamp} or {@code null} if the {@code Span} is still active.
+ *
+ * @return the end {@code Timestamp} or {@code null} if the {@code Span} is still active.
+ * @since 0.5
+ */
+ @Nullable
+ /*@Deterministic*/
+ public abstract Timestamp getEndTimestamp();
+
+ SpanData() {}
+
+ /**
+ * A timed event representation.
+ *
+ * @param <T> the type of value that is timed.
+ * @since 0.5
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class TimedEvent<T> {
+ /**
+ * Returns a new immutable {@code TimedEvent<T>}.
+ *
+ * @param timestamp the {@code Timestamp} of this event.
+ * @param event the event.
+ * @param <T> the type of value that is timed.
+ * @return a new immutable {@code TimedEvent<T>}
+ * @since 0.5
+ */
+ public static <T> TimedEvent<T> create(Timestamp timestamp, T event) {
+ return new AutoValue_SpanData_TimedEvent<T>(timestamp, event);
+ }
+
+ /**
+ * Returns the {@code Timestamp} of this event.
+ *
+ * @return the {@code Timestamp} of this event.
+ * @since 0.5
+ */
+ public abstract Timestamp getTimestamp();
+
+ /**
+ * Returns the event.
+ *
+ * @return the event.
+ * @since 0.5
+ */
+ /*@Deterministic*/
+ public abstract T getEvent();
+
+ TimedEvent() {}
+ }
+
+ /**
+ * A list of timed events and the number of dropped events representation.
+ *
+ * @param <T> the type of value that is timed.
+ * @since 0.5
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class TimedEvents<T> {
+ /**
+ * Returns a new immutable {@code TimedEvents<T>}.
+ *
+ * @param events the list of events.
+ * @param droppedEventsCount the number of dropped events.
+ * @param <T> the type of value that is timed.
+ * @return a new immutable {@code TimedEvents<T>}
+ * @since 0.5
+ */
+ public static <T> TimedEvents<T> create(List<TimedEvent<T>> events, int droppedEventsCount) {
+ return new AutoValue_SpanData_TimedEvents<T>(
+ Collections.unmodifiableList(
+ new ArrayList<TimedEvent<T>>(Utils.checkNotNull(events, "events"))),
+ droppedEventsCount);
+ }
+
+ /**
+ * Returns the list of events.
+ *
+ * @return the list of events.
+ * @since 0.5
+ */
+ public abstract List<TimedEvent<T>> getEvents();
+
+ /**
+ * Returns the number of dropped events.
+ *
+ * @return the number of dropped events.
+ * @since 0.5
+ */
+ public abstract int getDroppedEventsCount();
+
+ TimedEvents() {}
+ }
+
+ /**
+ * A set of attributes and the number of dropped attributes representation.
+ *
+ * @since 0.5
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Attributes {
+ /**
+ * Returns a new immutable {@code Attributes}.
+ *
+ * @param attributeMap the set of attributes.
+ * @param droppedAttributesCount the number of dropped attributes.
+ * @return a new immutable {@code Attributes}.
+ * @since 0.5
+ */
+ public static Attributes create(
+ Map<String, AttributeValue> attributeMap, int droppedAttributesCount) {
+ // TODO(bdrutu): Consider to use LinkedHashMap here and everywhere else, less test flakes
+ // for others on account of determinism.
+ return new AutoValue_SpanData_Attributes(
+ Collections.unmodifiableMap(
+ new HashMap<String, AttributeValue>(
+ Utils.checkNotNull(attributeMap, "attributeMap"))),
+ droppedAttributesCount);
+ }
+
+ /**
+ * Returns the set of attributes.
+ *
+ * @return the set of attributes.
+ * @since 0.5
+ */
+ public abstract Map<String, AttributeValue> getAttributeMap();
+
+ /**
+ * Returns the number of dropped attributes.
+ *
+ * @return the number of dropped attributes.
+ * @since 0.5
+ */
+ public abstract int getDroppedAttributesCount();
+
+ Attributes() {}
+ }
+
+ /**
+ * A list of links and the number of dropped links representation.
+ *
+ * @since 0.5
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Links {
+ /**
+ * Returns a new immutable {@code Links}.
+ *
+ * @param links the list of links.
+ * @param droppedLinksCount the number of dropped links.
+ * @return a new immutable {@code Links}.
+ * @since 0.5
+ */
+ public static Links create(List<Link> links, int droppedLinksCount) {
+ return new AutoValue_SpanData_Links(
+ Collections.unmodifiableList(new ArrayList<Link>(Utils.checkNotNull(links, "links"))),
+ droppedLinksCount);
+ }
+
+ /**
+ * Returns the list of links.
+ *
+ * @return the list of links.
+ * @since 0.5
+ */
+ public abstract List<Link> getLinks();
+
+ /**
+ * Returns the number of dropped links.
+ *
+ * @return the number of dropped links.
+ * @since 0.5
+ */
+ public abstract int getDroppedLinksCount();
+
+ Links() {}
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/export/SpanExporter.java b/api/src/main/java/io/opencensus/trace/export/SpanExporter.java
new file mode 100644
index 00000000..73ac5265
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/export/SpanExporter.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import io.opencensus.trace.Span;
+import io.opencensus.trace.TraceOptions;
+import java.util.Collection;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A service that is used by the library to export {@code SpanData} for all the spans that are part
+ * of a distributed sampled trace (see {@link TraceOptions#isSampled()}).
+ *
+ * @since 0.5
+ */
+@ThreadSafe
+public abstract class SpanExporter {
+ private static final SpanExporter NOOP_SPAN_EXPORTER = new NoopSpanExporter();
+
+ /**
+ * Returns the no-op implementation of the {@code ExportComponent}.
+ *
+ * @return the no-op implementation of the {@code ExportComponent}.
+ * @since 0.5
+ */
+ public static SpanExporter getNoopSpanExporter() {
+ return NOOP_SPAN_EXPORTER;
+ }
+
+ /**
+ * Registers a new service handler that is used by the library to export {@code SpanData} for
+ * sampled spans (see {@link TraceOptions#isSampled()}).
+ *
+ * @param name the name of the service handler. Must be unique for each service.
+ * @param handler the service handler that is called for each ended sampled span.
+ * @since 0.5
+ */
+ public abstract void registerHandler(String name, Handler handler);
+
+ /**
+ * Unregisters the service handler with the provided name.
+ *
+ * @param name the name of the service handler that will be unregistered.
+ * @since 0.5
+ */
+ public abstract void unregisterHandler(String name);
+
+ /**
+ * An abstract class that allows different tracing services to export recorded data for sampled
+ * spans in their own format.
+ *
+ * <p>To export data this MUST be register to to the ExportComponent using {@link
+ * #registerHandler(String, Handler)}.
+ *
+ * @since 0.5
+ */
+ public abstract static class Handler {
+
+ /**
+ * Exports a list of sampled (see {@link TraceOptions#isSampled()}) {@link Span}s using the
+ * immutable representation {@link SpanData}.
+ *
+ * <p>This may be called from a different thread than the one that called {@link Span#end()}.
+ *
+ * <p>Implementation SHOULD not block the calling thread. It should execute the export on a
+ * different thread if possible.
+ *
+ * @param spanDataList a list of {@code SpanData} objects to be exported.
+ * @since 0.5
+ */
+ public abstract void export(Collection<SpanData> spanDataList);
+ }
+
+ private static final class NoopSpanExporter extends SpanExporter {
+
+ @Override
+ public void registerHandler(String name, Handler handler) {}
+
+ @Override
+ public void unregisterHandler(String name) {}
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/internal/BaseMessageEventUtils.java b/api/src/main/java/io/opencensus/trace/internal/BaseMessageEventUtils.java
new file mode 100644
index 00000000..9d22a1c6
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/internal/BaseMessageEventUtils.java
@@ -0,0 +1,81 @@
+/*
+ * 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.trace.internal;
+
+import io.opencensus.common.Internal;
+import io.opencensus.internal.Utils;
+
+/**
+ * Helper class to convert/cast between for {@link io.opencensus.trace.MessageEvent} and {@link
+ * io.opencensus.trace.NetworkEvent}.
+ */
+@Internal
+@SuppressWarnings("deprecation")
+public final class BaseMessageEventUtils {
+ /**
+ * Cast or convert a {@link io.opencensus.trace.BaseMessageEvent} to {@link
+ * io.opencensus.trace.MessageEvent}.
+ *
+ * <p>Warning: if the input is a {@code io.opencensus.trace.NetworkEvent} and contains {@code
+ * kernelTimestamp} information, this information will be dropped.
+ *
+ * @param event the {@code BaseMessageEvent} that is being cast or converted.
+ * @return a {@code MessageEvent} representation of the input.
+ */
+ public static io.opencensus.trace.MessageEvent asMessageEvent(
+ io.opencensus.trace.BaseMessageEvent event) {
+ Utils.checkNotNull(event, "event");
+ if (event instanceof io.opencensus.trace.MessageEvent) {
+ return (io.opencensus.trace.MessageEvent) event;
+ }
+ io.opencensus.trace.NetworkEvent networkEvent = (io.opencensus.trace.NetworkEvent) event;
+ io.opencensus.trace.MessageEvent.Type type =
+ (networkEvent.getType() == io.opencensus.trace.NetworkEvent.Type.RECV)
+ ? io.opencensus.trace.MessageEvent.Type.RECEIVED
+ : io.opencensus.trace.MessageEvent.Type.SENT;
+ return io.opencensus.trace.MessageEvent.builder(type, networkEvent.getMessageId())
+ .setUncompressedMessageSize(networkEvent.getUncompressedMessageSize())
+ .setCompressedMessageSize(networkEvent.getCompressedMessageSize())
+ .build();
+ }
+
+ /**
+ * Cast or convert a {@link io.opencensus.trace.BaseMessageEvent} to {@link
+ * io.opencensus.trace.NetworkEvent}.
+ *
+ * @param event the {@code BaseMessageEvent} that is being cast or converted.
+ * @return a {@code io.opencensus.trace.NetworkEvent} representation of the input.
+ */
+ public static io.opencensus.trace.NetworkEvent asNetworkEvent(
+ io.opencensus.trace.BaseMessageEvent event) {
+ Utils.checkNotNull(event, "event");
+ if (event instanceof io.opencensus.trace.NetworkEvent) {
+ return (io.opencensus.trace.NetworkEvent) event;
+ }
+ io.opencensus.trace.MessageEvent messageEvent = (io.opencensus.trace.MessageEvent) event;
+ io.opencensus.trace.NetworkEvent.Type type =
+ (messageEvent.getType() == io.opencensus.trace.MessageEvent.Type.RECEIVED)
+ ? io.opencensus.trace.NetworkEvent.Type.RECV
+ : io.opencensus.trace.NetworkEvent.Type.SENT;
+ return io.opencensus.trace.NetworkEvent.builder(type, messageEvent.getMessageId())
+ .setUncompressedMessageSize(messageEvent.getUncompressedMessageSize())
+ .setCompressedMessageSize(messageEvent.getCompressedMessageSize())
+ .build();
+ }
+
+ private BaseMessageEventUtils() {}
+}
diff --git a/api/src/main/java/io/opencensus/trace/package-info.java b/api/src/main/java/io/opencensus/trace/package-info.java
new file mode 100644
index 00000000..77f39aba
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+/**
+ * API for distributed tracing.
+ *
+ * <p>Distributed tracing, also called distributed request tracing, is a technique that helps
+ * debugging distributed applications.
+ *
+ * <p>Trace represents a tree of spans. A trace has a root span that encapsulates all the spans from
+ * start to end, and the children spans being the distinct calls invoked in between.
+ *
+ * <p>{@link io.opencensus.trace.Span} represents a single operation within a trace.
+ *
+ * <p>{@link io.opencensus.trace.Span Spans} are propagated in-process in the {@code
+ * io.grpc.Context} and between process using one of the wire propagation formats supported in the
+ * {@code io.opencensus.trace.propagation} package.
+ */
+// TODO: Add code examples.
+package io.opencensus.trace;
diff --git a/api/src/main/java/io/opencensus/trace/propagation/BinaryFormat.java b/api/src/main/java/io/opencensus/trace/propagation/BinaryFormat.java
new file mode 100644
index 00000000..7e875fd6
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/propagation/BinaryFormat.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.SpanContext;
+import java.text.ParseException;
+
+/**
+ * This is a helper class for {@link SpanContext} propagation on the wire using binary encoding.
+ *
+ * <p>Example of usage on the client:
+ *
+ * <pre>{@code
+ * private static final Tracer tracer = Tracing.getTracer();
+ * private static final BinaryFormat binaryFormat =
+ * Tracing.getPropagationComponent().getBinaryFormat();
+ * void onSendRequest() {
+ * try (Scope ss = tracer.spanBuilder("Sent.MyRequest").startScopedSpan()) {
+ * byte[] binaryValue = binaryFormat.toByteArray(tracer.getCurrentContext().context());
+ * // Send the request including the binaryValue and wait for the response.
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>Example of usage on the server:
+ *
+ * <pre>{@code
+ * private static final Tracer tracer = Tracing.getTracer();
+ * private static final BinaryFormat binaryFormat =
+ * Tracing.getPropagationComponent().getBinaryFormat();
+ * void onRequestReceived() {
+ * // Get the binaryValue from the request.
+ * SpanContext spanContext = SpanContext.INVALID;
+ * try {
+ * if (binaryValue != null) {
+ * spanContext = binaryFormat.fromByteArray(binaryValue);
+ * }
+ * } catch (SpanContextParseException e) {
+ * // Maybe log the exception.
+ * }
+ * try (Scope ss =
+ * tracer.spanBuilderWithRemoteParent("Recv.MyRequest", spanContext).startScopedSpan()) {
+ * // Handle request and send response back.
+ * }
+ * }
+ * }</pre>
+ *
+ * @since 0.5
+ */
+public abstract class BinaryFormat {
+ static final NoopBinaryFormat NOOP_BINARY_FORMAT = new NoopBinaryFormat();
+
+ /**
+ * Serializes a {@link SpanContext} into a byte array using the binary format.
+ *
+ * @deprecated use {@link #toByteArray(SpanContext)}.
+ * @param spanContext the {@code SpanContext} to serialize.
+ * @return the serialized binary value.
+ * @throws NullPointerException if the {@code spanContext} is {@code null}.
+ * @since 0.5
+ */
+ @Deprecated
+ public byte[] toBinaryValue(SpanContext spanContext) {
+ return toByteArray(spanContext);
+ }
+
+ /**
+ * Serializes a {@link SpanContext} into a byte array using the binary format.
+ *
+ * @param spanContext the {@code SpanContext} to serialize.
+ * @return the serialized binary value.
+ * @throws NullPointerException if the {@code spanContext} is {@code null}.
+ * @since 0.7
+ */
+ public byte[] toByteArray(SpanContext spanContext) {
+ // Implementation must override this method.
+ return toBinaryValue(spanContext);
+ }
+
+ /**
+ * Parses the {@link SpanContext} from a byte array using the binary format.
+ *
+ * @deprecated use {@link #fromByteArray(byte[])}.
+ * @param bytes a binary encoded buffer from which the {@code SpanContext} will be parsed.
+ * @return the parsed {@code SpanContext}.
+ * @throws NullPointerException if the {@code input} is {@code null}.
+ * @throws ParseException if the version is not supported or the input is invalid
+ * @since 0.5
+ */
+ @Deprecated
+ public SpanContext fromBinaryValue(byte[] bytes) throws ParseException {
+ try {
+ return fromByteArray(bytes);
+ } catch (SpanContextParseException e) {
+ throw new ParseException(e.toString(), 0);
+ }
+ }
+
+ /**
+ * Parses the {@link SpanContext} from a byte array using the binary format.
+ *
+ * @param bytes a binary encoded buffer from which the {@code SpanContext} will be parsed.
+ * @return the parsed {@code SpanContext}.
+ * @throws NullPointerException if the {@code input} is {@code null}.
+ * @throws SpanContextParseException if the version is not supported or the input is invalid
+ * @since 0.7
+ */
+ public SpanContext fromByteArray(byte[] bytes) throws SpanContextParseException {
+ // Implementation must override this method. If it doesn't, the below will StackOverflowError.
+ try {
+ return fromBinaryValue(bytes);
+ } catch (ParseException e) {
+ throw new SpanContextParseException("Error while parsing.", e);
+ }
+ }
+
+ /**
+ * Returns the no-op implementation of the {@code BinaryFormat}.
+ *
+ * @return the no-op implementation of the {@code BinaryFormat}.
+ */
+ static BinaryFormat getNoopBinaryFormat() {
+ return NOOP_BINARY_FORMAT;
+ }
+
+ private static final class NoopBinaryFormat extends BinaryFormat {
+ @Override
+ public byte[] toByteArray(SpanContext spanContext) {
+ Utils.checkNotNull(spanContext, "spanContext");
+ return new byte[0];
+ }
+
+ @Override
+ public SpanContext fromByteArray(byte[] bytes) {
+ Utils.checkNotNull(bytes, "bytes");
+ return SpanContext.INVALID;
+ }
+
+ private NoopBinaryFormat() {}
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/propagation/PropagationComponent.java b/api/src/main/java/io/opencensus/trace/propagation/PropagationComponent.java
new file mode 100644
index 00000000..a90f0419
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/propagation/PropagationComponent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+import io.opencensus.common.ExperimentalApi;
+
+/**
+ * Container class for all the supported propagation formats. Currently supports only Binary format
+ * (see {@link BinaryFormat}) and B3 Text format (see {@link TextFormat}) but more formats will be
+ * added.
+ *
+ * @since 0.5
+ */
+public abstract class PropagationComponent {
+ private static final PropagationComponent NOOP_PROPAGATION_COMPONENT =
+ new NoopPropagationComponent();
+
+ /**
+ * Returns the {@link BinaryFormat} with the provided implementations. If no implementation is
+ * provided then no-op implementation will be used.
+ *
+ * @return the {@code BinaryFormat} implementation.
+ * @since 0.5
+ */
+ public abstract BinaryFormat getBinaryFormat();
+
+ /**
+ * Returns the B3 {@link TextFormat} with the provided implementations. See <a
+ * href="https://github.com/openzipkin/b3-propagation">b3-propagation</a> for more information. If
+ * no implementation is provided then no-op implementation will be used.
+ *
+ * @since 0.11.0
+ * @return the B3 {@code TextFormat} implementation for B3.
+ */
+ @ExperimentalApi
+ public abstract TextFormat getB3Format();
+
+ /**
+ * Returns an instance that contains no-op implementations for all the instances.
+ *
+ * @return an instance that contains no-op implementations for all the instances.
+ * @since 0.5
+ */
+ public static PropagationComponent getNoopPropagationComponent() {
+ return NOOP_PROPAGATION_COMPONENT;
+ }
+
+ private static final class NoopPropagationComponent extends PropagationComponent {
+ @Override
+ public BinaryFormat getBinaryFormat() {
+ return BinaryFormat.getNoopBinaryFormat();
+ }
+
+ @Override
+ public TextFormat getB3Format() {
+ return TextFormat.getNoopTextFormat();
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/propagation/SpanContextParseException.java b/api/src/main/java/io/opencensus/trace/propagation/SpanContextParseException.java
new file mode 100644
index 00000000..80d42af5
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/propagation/SpanContextParseException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+/**
+ * Exception thrown when a {@link io.opencensus.trace.SpanContext} cannot be parsed.
+ *
+ * @since 0.7
+ */
+public final class SpanContextParseException extends Exception {
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructs a new {@code SpanContextParseException} with the given message.
+ *
+ * @param message a message describing the parse error.
+ * @since 0.7
+ */
+ public SpanContextParseException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code SpanContextParseException} with the given message and cause.
+ *
+ * @param message a message describing the parse error.
+ * @param cause the cause of the parse error.
+ * @since 0.7
+ */
+ public SpanContextParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/propagation/TextFormat.java b/api/src/main/java/io/opencensus/trace/propagation/TextFormat.java
new file mode 100644
index 00000000..d52e71f1
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/propagation/TextFormat.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+import io.opencensus.common.ExperimentalApi;
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.SpanContext;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.NonNull;
+*/
+
+/**
+ * Injects and extracts {@link SpanContext trace identifiers} as text into carriers that travel
+ * in-band across process boundaries. Identifiers are often encoded as messaging or RPC request
+ * headers.
+ *
+ * <p>When using http, the carrier of propagated data on both the client (injector) and server
+ * (extractor) side is usually an http request. Propagation is usually implemented via library-
+ * specific request interceptors, where the client-side injects span identifiers and the server-side
+ * extracts them.
+ *
+ * <p>Example of usage on the client:
+ *
+ * <pre>{@code
+ * private static final Tracer tracer = Tracing.getTracer();
+ * private static final TextFormat textFormat = Tracing.getPropagationComponent().getTextFormat();
+ * private static final TextFormat.Setter setter = new TextFormat.Setter<HttpURLConnection>() {
+ * public void put(HttpURLConnection carrier, String key, String value) {
+ * carrier.setRequestProperty(field, value);
+ * }
+ * }
+ *
+ * void makeHttpRequest() {
+ * Span span = tracer.spanBuilder("Sent.MyRequest").startSpan();
+ * try (Scope s = tracer.withSpan(span)) {
+ * HttpURLConnection connection =
+ * (HttpURLConnection) new URL("http://myserver").openConnection();
+ * textFormat.inject(span.getContext(), connection, httpURLConnectionSetter);
+ * // Send the request, wait for response and maybe set the status if not ok.
+ * }
+ * span.end(); // Can set a status.
+ * }
+ * }</pre>
+ *
+ * <p>Example of usage on the server:
+ *
+ * <pre>{@code
+ * private static final Tracer tracer = Tracing.getTracer();
+ * private static final TextFormat textFormat = Tracing.getPropagationComponent().getTextFormat();
+ * private static final TextFormat.Getter<HttpRequest> getter = ...;
+ *
+ * void onRequestReceived(HttpRequest request) {
+ * SpanContext spanContext = textFormat.extract(request, getter);
+ * Span span = tracer.spanBuilderWithRemoteParent("Recv.MyRequest", spanContext).startSpan();
+ * try (Scope s = tracer.withSpan(span)) {
+ * // Handle request and send response back.
+ * }
+ * span.end()
+ * }
+ * }</pre>
+ *
+ * @since 0.11
+ */
+@ExperimentalApi
+public abstract class TextFormat {
+ private static final NoopTextFormat NOOP_TEXT_FORMAT = new NoopTextFormat();
+
+ /**
+ * The propagation fields defined. If your carrier is reused, you should delete the fields here
+ * before calling {@link #inject(SpanContext, Object, Setter)}.
+ *
+ * <p>For example, if the carrier is a single-use or immutable request object, you don't need to
+ * clear fields as they couldn't have been set before. If it is a mutable, retryable object,
+ * successive calls should clear these fields first.
+ *
+ * @since 0.11
+ */
+ // The use cases of this are:
+ // * allow pre-allocation of fields, especially in systems like gRPC Metadata
+ // * allow a single-pass over an iterator (ex OpenTracing has no getter in TextMap)
+ public abstract List<String> fields();
+
+ /**
+ * Injects the span context downstream. For example, as http headers.
+ *
+ * @param spanContext possibly not sampled.
+ * @param carrier holds propagation fields. For example, an outgoing message or http request.
+ * @param setter invoked for each propagation key to add or remove.
+ * @since 0.11
+ */
+ public abstract <C /*>>> extends @NonNull Object*/> void inject(
+ SpanContext spanContext, C carrier, Setter<C> setter);
+
+ /**
+ * Class that allows a {@code TextFormat} to set propagated fields into a carrier.
+ *
+ * <p>{@code Setter} is stateless and allows to be saved as a constant to avoid runtime
+ * allocations.
+ *
+ * @param <C> carrier of propagation fields, such as an http request
+ * @since 0.11
+ */
+ public abstract static class Setter<C> {
+
+ /**
+ * Replaces a propagated field with the given value.
+ *
+ * <p>For example, a setter for an {@link java.net.HttpURLConnection} would be the method
+ * reference {@link java.net.HttpURLConnection#addRequestProperty(String, String)}
+ *
+ * @param carrier holds propagation fields. For example, an outgoing message or http request.
+ * @param key the key of the field.
+ * @param value the value of the field.
+ * @since 0.11
+ */
+ public abstract void put(C carrier, String key, String value);
+ }
+
+ /**
+ * Extracts the span context from upstream. For example, as http headers.
+ *
+ * @param carrier holds propagation fields. For example, an outgoing message or http request.
+ * @param getter invoked for each propagation key to get.
+ * @throws SpanContextParseException if the input is invalid
+ * @since 0.11
+ */
+ public abstract <C /*>>> extends @NonNull Object*/> SpanContext extract(
+ C carrier, Getter<C> getter) throws SpanContextParseException;
+
+ /**
+ * Class that allows a {@code TextFormat} to read propagated fields from a carrier.
+ *
+ * <p>{@code Getter} is stateless and allows to be saved as a constant to avoid runtime
+ * allocations.
+ *
+ * @param <C> carrier of propagation fields, such as an http request
+ * @since 0.11
+ */
+ public abstract static class Getter<C> {
+
+ /**
+ * Returns the first value of the given propagation {@code key} or returns {@code null}.
+ *
+ * @param carrier carrier of propagation fields, such as an http request
+ * @param key the key of the field.
+ * @return the first value of the given propagation {@code key} or returns {@code null}.
+ * @since 0.11
+ */
+ @Nullable
+ public abstract String get(C carrier, String key);
+ }
+
+ /**
+ * Returns the no-op implementation of the {@code TextFormat}.
+ *
+ * @return the no-op implementation of the {@code TextFormat}.
+ */
+ static TextFormat getNoopTextFormat() {
+ return NOOP_TEXT_FORMAT;
+ }
+
+ private static final class NoopTextFormat extends TextFormat {
+
+ private NoopTextFormat() {}
+
+ @Override
+ public List<String> fields() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public <C /*>>> extends @NonNull Object*/> void inject(
+ SpanContext spanContext, C carrier, Setter<C> setter) {
+ Utils.checkNotNull(spanContext, "spanContext");
+ Utils.checkNotNull(carrier, "carrier");
+ Utils.checkNotNull(setter, "setter");
+ }
+
+ @Override
+ public <C /*>>> extends @NonNull Object*/> SpanContext extract(C carrier, Getter<C> getter) {
+ Utils.checkNotNull(carrier, "carrier");
+ Utils.checkNotNull(getter, "getter");
+ return SpanContext.INVALID;
+ }
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/samplers/AlwaysSampleSampler.java b/api/src/main/java/io/opencensus/trace/samplers/AlwaysSampleSampler.java
new file mode 100644
index 00000000..7b61e235
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/samplers/AlwaysSampleSampler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017, 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.trace.samplers;
+
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/** Sampler that always makes a "yes" decision on {@link Span} sampling. */
+@Immutable
+final class AlwaysSampleSampler extends Sampler {
+
+ AlwaysSampleSampler() {}
+
+ // Returns always makes a "yes" decision on {@link Span} sampling.
+ @Override
+ public boolean shouldSample(
+ @Nullable SpanContext parentContext,
+ @Nullable Boolean hasRemoteParent,
+ TraceId traceId,
+ SpanId spanId,
+ String name,
+ List<Span> parentLinks) {
+ return true;
+ }
+
+ @Override
+ public String getDescription() {
+ return toString();
+ }
+
+ @Override
+ public String toString() {
+ return "AlwaysSampleSampler";
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/samplers/NeverSampleSampler.java b/api/src/main/java/io/opencensus/trace/samplers/NeverSampleSampler.java
new file mode 100644
index 00000000..c6de645a
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/samplers/NeverSampleSampler.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017, 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.trace.samplers;
+
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/** Sampler that always makes a "no" decision on {@link Span} sampling. */
+@Immutable
+final class NeverSampleSampler extends Sampler {
+
+ NeverSampleSampler() {}
+
+ // Returns always makes a "no" decision on {@link Span} sampling.
+ @Override
+ public boolean shouldSample(
+ @Nullable SpanContext parentContext,
+ @Nullable Boolean hasRemoteParent,
+ TraceId traceId,
+ SpanId spanId,
+ String name,
+ List<Span> parentLinks) {
+ return false;
+ }
+
+ @Override
+ public String getDescription() {
+ return toString();
+ }
+
+ @Override
+ public String toString() {
+ return "NeverSampleSampler";
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/samplers/ProbabilitySampler.java b/api/src/main/java/io/opencensus/trace/samplers/ProbabilitySampler.java
new file mode 100644
index 00000000..b9c18e00
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/samplers/ProbabilitySampler.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2017, 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.trace.samplers;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.internal.Utils;
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * We assume the lower 64 bits of the traceId's are randomly distributed around the whole (long)
+ * range. We convert an incoming probability into an upper bound on that value, such that we can
+ * just compare the absolute value of the id and the bound to see if we are within the desired
+ * probability range. Using the low bits of the traceId also ensures that systems that only use 64
+ * bit ID's will also work with this sampler.
+ */
+@AutoValue
+@Immutable
+abstract class ProbabilitySampler extends Sampler {
+
+ ProbabilitySampler() {}
+
+ abstract double getProbability();
+
+ abstract long getIdUpperBound();
+
+ /**
+ * Returns a new {@link ProbabilitySampler}. The probability of sampling a trace is equal to that
+ * of the specified probability.
+ *
+ * @param probability The desired probability of sampling. Must be within [0.0, 1.0].
+ * @return a new {@link ProbabilitySampler}.
+ * @throws IllegalArgumentException if {@code probability} is out of range
+ */
+ static ProbabilitySampler create(double probability) {
+ Utils.checkArgument(
+ probability >= 0.0 && probability <= 1.0, "probability must be in range [0.0, 1.0]");
+ long idUpperBound;
+ // Special case the limits, to avoid any possible issues with lack of precision across
+ // double/long boundaries. For probability == 0.0, we use Long.MIN_VALUE as this guarantees
+ // that we will never sample a trace, even in the case where the id == Long.MIN_VALUE, since
+ // Math.Abs(Long.MIN_VALUE) == Long.MIN_VALUE.
+ if (probability == 0.0) {
+ idUpperBound = Long.MIN_VALUE;
+ } else if (probability == 1.0) {
+ idUpperBound = Long.MAX_VALUE;
+ } else {
+ idUpperBound = (long) (probability * Long.MAX_VALUE);
+ }
+ return new AutoValue_ProbabilitySampler(probability, idUpperBound);
+ }
+
+ @Override
+ public final boolean shouldSample(
+ @Nullable SpanContext parentContext,
+ @Nullable Boolean hasRemoteParent,
+ TraceId traceId,
+ SpanId spanId,
+ String name,
+ @Nullable List<Span> parentLinks) {
+ // If the parent is sampled keep the sampling decision.
+ if (parentContext != null && parentContext.getTraceOptions().isSampled()) {
+ return true;
+ }
+ if (parentLinks != null) {
+ // If any parent link is sampled keep the sampling decision.
+ for (Span parentLink : parentLinks) {
+ if (parentLink.getContext().getTraceOptions().isSampled()) {
+ return true;
+ }
+ }
+ }
+ // Always sample if we are within probability range. This is true even for child spans (that
+ // may have had a different sampling decision made) to allow for different sampling policies,
+ // and dynamic increases to sampling probabilities for debugging purposes.
+ // Note use of '<' for comparison. This ensures that we never sample for probability == 0.0,
+ // while allowing for a (very) small chance of *not* sampling if the id == Long.MAX_VALUE.
+ // This is considered a reasonable tradeoff for the simplicity/performance requirements (this
+ // code is executed in-line for every Span creation).
+ return Math.abs(traceId.getLowerLong()) < getIdUpperBound();
+ }
+
+ @Override
+ public final String getDescription() {
+ return String.format("ProbabilitySampler{%.6f}", getProbability());
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/samplers/Samplers.java b/api/src/main/java/io/opencensus/trace/samplers/Samplers.java
new file mode 100644
index 00000000..c10610a0
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/samplers/Samplers.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017, 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.trace.samplers;
+
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+
+/**
+ * Static class to access a set of pre-defined {@link Sampler Samplers}.
+ *
+ * @since 0.5
+ */
+public final class Samplers {
+ private static final Sampler ALWAYS_SAMPLE = new AlwaysSampleSampler();
+ private static final Sampler NEVER_SAMPLE = new NeverSampleSampler();
+
+ // No instance of this class.
+ private Samplers() {}
+
+ /**
+ * Returns a {@link Sampler} that always makes a "yes" decision on {@link Span} sampling.
+ *
+ * @return a {@code Sampler} that always makes a "yes" decision on {@code Span} sampling.
+ * @since 0.5
+ */
+ public static Sampler alwaysSample() {
+ return ALWAYS_SAMPLE;
+ }
+
+ /**
+ * Returns a {@link Sampler} that always makes a "no" decision on {@link Span} sampling.
+ *
+ * @return a {@code Sampler} that always makes a "no" decision on {@code Span} sampling.
+ * @since 0.5
+ */
+ public static Sampler neverSample() {
+ return NEVER_SAMPLE;
+ }
+
+ /**
+ * Returns a {@link Sampler} that makes a "yes" decision with a given probability.
+ *
+ * @param probability The desired probability of sampling. Must be within [0.0, 1.0].
+ * @return a {@code Sampler} that makes a "yes" decision with a given probability.
+ * @throws IllegalArgumentException if {@code probability} is out of range
+ * @since 0.5
+ */
+ public static Sampler probabilitySampler(double probability) {
+ return ProbabilitySampler.create(probability);
+ }
+}
diff --git a/api/src/main/java/io/opencensus/trace/unsafe/ContextUtils.java b/api/src/main/java/io/opencensus/trace/unsafe/ContextUtils.java
new file mode 100644
index 00000000..3f4b9889
--- /dev/null
+++ b/api/src/main/java/io/opencensus/trace/unsafe/ContextUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017, 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.trace.unsafe;
+
+import io.grpc.Context;
+import io.opencensus.trace.Span;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/**
+ * Util methods/functionality to interact with the {@link io.grpc.Context}.
+ *
+ * <p>Users must interact with the current Context via the public APIs in {@link
+ * io.opencensus.trace.Tracer} and avoid usages of the {@link #CONTEXT_SPAN_KEY} directly.
+ *
+ * @since 0.5
+ */
+public final class ContextUtils {
+ // No instance of this class.
+ private ContextUtils() {}
+
+ /**
+ * The {@link io.grpc.Context.Key} used to interact with {@link io.grpc.Context}.
+ *
+ * @since 0.5
+ */
+ public static final Context.Key</*@Nullable*/ Span> CONTEXT_SPAN_KEY =
+ Context.key("opencensus-trace-span-key");
+}
diff --git a/api/src/test/java/io/opencensus/common/DurationTest.java b/api/src/test/java/io/opencensus/common/DurationTest.java
new file mode 100644
index 00000000..ea636ca0
--- /dev/null
+++ b/api/src/test/java/io/opencensus/common/DurationTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2017, 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Duration}. */
+@RunWith(JUnit4.class)
+public class DurationTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testDurationCreate() {
+ assertThat(Duration.create(24, 42).getSeconds()).isEqualTo(24);
+ assertThat(Duration.create(24, 42).getNanos()).isEqualTo(42);
+ assertThat(Duration.create(-24, -42).getSeconds()).isEqualTo(-24);
+ assertThat(Duration.create(-24, -42).getNanos()).isEqualTo(-42);
+ assertThat(Duration.create(315576000000L, 999999999).getSeconds()).isEqualTo(315576000000L);
+ assertThat(Duration.create(315576000000L, 999999999).getNanos()).isEqualTo(999999999);
+ assertThat(Duration.create(-315576000000L, -999999999).getSeconds()).isEqualTo(-315576000000L);
+ assertThat(Duration.create(-315576000000L, -999999999).getNanos()).isEqualTo(-999999999);
+ }
+
+ @Test
+ public void create_SecondsTooLow() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is less than minimum (-315576000000): -315576000001");
+ Duration.create(-315576000001L, 0);
+ }
+
+ @Test
+ public void create_SecondsTooHigh() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is greater than maximum (315576000000): 315576000001");
+ Duration.create(315576000001L, 0);
+ }
+
+ @Test
+ public void create_NanosTooLow() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'nanos' is less than minimum (-999999999): -1000000000");
+ Duration.create(0, -1000000000);
+ }
+
+ @Test
+ public void create_NanosTooHigh() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'nanos' is greater than maximum (999999999): 1000000000");
+ Duration.create(0, 1000000000);
+ }
+
+ @Test
+ public void create_NegativeSecondsPositiveNanos() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' and 'nanos' have inconsistent sign: seconds=-1, nanos=1");
+ Duration.create(-1, 1);
+ }
+
+ @Test
+ public void create_PositiveSecondsNegativeNanos() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' and 'nanos' have inconsistent sign: seconds=1, nanos=-1");
+ Duration.create(1, -1);
+ }
+
+ @Test
+ public void testDurationFromMillis() {
+ assertThat(Duration.fromMillis(0)).isEqualTo(Duration.create(0, 0));
+ assertThat(Duration.fromMillis(987)).isEqualTo(Duration.create(0, 987000000));
+ assertThat(Duration.fromMillis(3456)).isEqualTo(Duration.create(3, 456000000));
+ }
+
+ @Test
+ public void testDurationFromMillisNegative() {
+ assertThat(Duration.fromMillis(-1)).isEqualTo(Duration.create(0, -1000000));
+ assertThat(Duration.fromMillis(-999)).isEqualTo(Duration.create(0, -999000000));
+ assertThat(Duration.fromMillis(-1000)).isEqualTo(Duration.create(-1, 0));
+ assertThat(Duration.fromMillis(-3456)).isEqualTo(Duration.create(-3, -456000000));
+ }
+
+ @Test
+ public void fromMillis_TooLow() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is less than minimum (-315576000000): -315576000001");
+ Duration.fromMillis(-315576000001000L);
+ }
+
+ @Test
+ public void fromMillis_TooHigh() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is greater than maximum (315576000000): 315576000001");
+ Duration.fromMillis(315576000001000L);
+ }
+
+ @Test
+ public void duration_CompareLength() {
+ assertThat(Duration.create(0, 0).compareTo(Duration.create(0, 0))).isEqualTo(0);
+ assertThat(Duration.create(24, 42).compareTo(Duration.create(24, 42))).isEqualTo(0);
+ assertThat(Duration.create(-24, -42).compareTo(Duration.create(-24, -42))).isEqualTo(0);
+ assertThat(Duration.create(25, 42).compareTo(Duration.create(24, 42))).isEqualTo(1);
+ assertThat(Duration.create(24, 45).compareTo(Duration.create(24, 42))).isEqualTo(1);
+ assertThat(Duration.create(24, 42).compareTo(Duration.create(25, 42))).isEqualTo(-1);
+ assertThat(Duration.create(24, 42).compareTo(Duration.create(24, 45))).isEqualTo(-1);
+ assertThat(Duration.create(-24, -45).compareTo(Duration.create(-24, -42))).isEqualTo(-1);
+ assertThat(Duration.create(-24, -42).compareTo(Duration.create(-25, -42))).isEqualTo(1);
+ assertThat(Duration.create(24, 42).compareTo(Duration.create(-24, -42))).isEqualTo(1);
+ }
+
+ @Test
+ public void testDurationEqual() {
+ // Positive tests.
+ assertThat(Duration.create(0, 0)).isEqualTo(Duration.create(0, 0));
+ assertThat(Duration.create(24, 42)).isEqualTo(Duration.create(24, 42));
+ assertThat(Duration.create(-24, -42)).isEqualTo(Duration.create(-24, -42));
+ // Negative tests.
+ assertThat(Duration.create(25, 42)).isNotEqualTo(Duration.create(24, 42));
+ assertThat(Duration.create(24, 43)).isNotEqualTo(Duration.create(24, 42));
+ assertThat(Duration.create(-25, -42)).isNotEqualTo(Duration.create(-24, -42));
+ assertThat(Duration.create(-24, -43)).isNotEqualTo(Duration.create(-24, -42));
+ }
+
+ @Test
+ public void toMillis() {
+ assertThat(Duration.create(10, 0).toMillis()).isEqualTo(10000L);
+ assertThat(Duration.create(10, 1000).toMillis()).isEqualTo(10000L);
+ assertThat(Duration.create(0, (int) 1e6).toMillis()).isEqualTo(1L);
+ assertThat(Duration.create(0, 0).toMillis()).isEqualTo(0L);
+ assertThat(Duration.create(-10, 0).toMillis()).isEqualTo(-10000L);
+ assertThat(Duration.create(-10, -1000).toMillis()).isEqualTo(-10000L);
+ assertThat(Duration.create(0, -(int) 1e6).toMillis()).isEqualTo(-1L);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/common/FunctionsTest.java b/api/src/test/java/io/opencensus/common/FunctionsTest.java
new file mode 100644
index 00000000..55d58d4d
--- /dev/null
+++ b/api/src/test/java/io/opencensus/common/FunctionsTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017, 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Functions}. */
+@RunWith(JUnit4.class)
+public class FunctionsTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testReturnNull() {
+ assertThat(Functions.returnNull().apply("ignored")).isNull();
+ }
+
+ @Test
+ public void testReturnConstant() {
+ assertThat(Functions.returnConstant(123).apply("ignored")).isEqualTo(123);
+ }
+
+ @Test
+ public void testReturnToString() {
+ assertThat(Functions.returnToString().apply("input")).isEqualTo("input");
+ assertThat(Functions.returnToString().apply(Boolean.FALSE)).isEqualTo("false");
+ assertThat(Functions.returnToString().apply(Double.valueOf(123.45))).isEqualTo("123.45");
+ assertThat(Functions.returnToString().apply(null)).isEqualTo(null);
+ }
+
+ @Test
+ public void testThrowIllegalArgumentException() {
+ Function<Object, Void> f = Functions.throwIllegalArgumentException();
+ thrown.expect(IllegalArgumentException.class);
+ f.apply("ignored");
+ }
+
+ @Test
+ public void testThrowAssertionError() {
+ Function<Object, Void> f = Functions.throwAssertionError();
+ thrown.handleAssertionErrors();
+ thrown.expect(AssertionError.class);
+ f.apply("ignored");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/common/ServerStatsEncodingTest.java b/api/src/test/java/io/opencensus/common/ServerStatsEncodingTest.java
new file mode 100644
index 00000000..6db14a79
--- /dev/null
+++ b/api/src/test/java/io/opencensus/common/ServerStatsEncodingTest.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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ServerStatsEncoding}. */
+@RunWith(JUnit4.class)
+public class ServerStatsEncodingTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void encodeDecodeTest() throws ServerStatsDeserializationException {
+ ServerStats serverStatsToBeEncoded = null;
+ ServerStats serverStatsDecoded = null;
+ byte[] serialized = null;
+
+ serverStatsToBeEncoded = ServerStats.create(31, 22, (byte) 1);
+ serialized = ServerStatsEncoding.toBytes(serverStatsToBeEncoded);
+ serverStatsDecoded = ServerStatsEncoding.parseBytes(serialized);
+ assertThat(serverStatsDecoded).isEqualTo(serverStatsToBeEncoded);
+
+ serverStatsToBeEncoded = ServerStats.create(0, 22, (byte) 0);
+ serialized = ServerStatsEncoding.toBytes(serverStatsToBeEncoded);
+ serverStatsDecoded = ServerStatsEncoding.parseBytes(serialized);
+ assertThat(serverStatsDecoded).isEqualTo(serverStatsToBeEncoded);
+
+ serverStatsToBeEncoded = ServerStats.create(450, 0, (byte) 0);
+ serialized = ServerStatsEncoding.toBytes(serverStatsToBeEncoded);
+ serverStatsDecoded = ServerStatsEncoding.parseBytes(serialized);
+ assertThat(serverStatsDecoded).isEqualTo(serverStatsToBeEncoded);
+ }
+
+ @Test
+ public void skipUnknownFieldTest() throws ServerStatsDeserializationException {
+ ServerStats serverStatsToBeEncoded = null;
+ ServerStats serverStatsDecoded = null;
+ byte[] serialized = null;
+
+ serverStatsToBeEncoded = ServerStats.create(31, 22, (byte) 1);
+ serialized = ServerStatsEncoding.toBytes(serverStatsToBeEncoded);
+
+ // Add new field at the end.
+ byte[] serializedExpanded = new byte[serialized.length + 9];
+ System.arraycopy(serialized, 0, serializedExpanded, 0, serialized.length);
+ final ByteBuffer bb = ByteBuffer.wrap(serializedExpanded);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ bb.position(serialized.length);
+ bb.put((byte) 255);
+ bb.putLong(0L);
+ byte[] newSerialized = bb.array();
+
+ serverStatsDecoded = ServerStatsEncoding.parseBytes(newSerialized);
+ assertThat(serverStatsDecoded).isEqualTo(serverStatsToBeEncoded);
+ }
+
+ @Test
+ public void negativeLbLatencyValueTest() throws ServerStatsDeserializationException {
+ ServerStats serverStatsToBeEncoded = null;
+ ServerStats serverStatsDecoded = null;
+ byte[] serialized = null;
+
+ serverStatsToBeEncoded = ServerStats.create(31, 22, (byte) 1);
+ serialized = ServerStatsEncoding.toBytes(serverStatsToBeEncoded);
+
+ // update serialized byte[] with negative value for lbLatency.
+ final ByteBuffer bb = ByteBuffer.wrap(serialized);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ bb.position(2);
+ bb.putLong(-100L);
+
+ byte[] newSerialized = bb.array();
+ thrown.expect(ServerStatsDeserializationException.class);
+ thrown.expectMessage("Serialized ServiceStats contains invalid values");
+ ServerStatsEncoding.parseBytes(newSerialized);
+ }
+
+ @Test
+ public void negativeServerLatencyValueTest() throws ServerStatsDeserializationException {
+ ServerStats serverStatsToBeEncoded = null;
+ ServerStats serverStatsDecoded = null;
+ byte[] serialized = null;
+
+ serverStatsToBeEncoded = ServerStats.create(31, 22, (byte) 1);
+ serialized = ServerStatsEncoding.toBytes(serverStatsToBeEncoded);
+
+ // update serialized byte[] with negative value for serviceLatency.
+ final ByteBuffer bb = ByteBuffer.wrap(serialized);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ bb.position(11);
+ bb.putLong(-101L);
+
+ byte[] newSerialized = bb.array();
+ thrown.expect(ServerStatsDeserializationException.class);
+ thrown.expectMessage("Serialized ServiceStats contains invalid values");
+ ServerStatsEncoding.parseBytes(newSerialized);
+ }
+
+ @Test
+ public void emptySerializedBuffer() throws ServerStatsDeserializationException {
+ final ByteBuffer bb = ByteBuffer.allocate(0);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+
+ byte[] newSerialized = bb.array();
+ thrown.expect(ServerStatsDeserializationException.class);
+ thrown.expectMessage("Serialized ServerStats buffer is empty");
+ ServerStatsEncoding.parseBytes(newSerialized);
+ }
+
+ @Test
+ public void invalidNegativeVersion() throws ServerStatsDeserializationException {
+ final ByteBuffer bb = ByteBuffer.allocate(10);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ bb.put((byte) -1);
+ byte[] newSerialized = bb.array();
+ thrown.expect(ServerStatsDeserializationException.class);
+ thrown.expectMessage("Invalid ServerStats version: -1");
+ ServerStatsEncoding.parseBytes(newSerialized);
+ }
+
+ @Test
+ public void invalidCompatibleVersion() throws ServerStatsDeserializationException {
+ final ByteBuffer bb = ByteBuffer.allocate(10);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ bb.put((byte) (ServerStatsEncoding.CURRENT_VERSION + 1));
+ byte[] newSerialized = bb.array();
+ thrown.expect(ServerStatsDeserializationException.class);
+ thrown.expectMessage(
+ "Invalid ServerStats version: " + (ServerStatsEncoding.CURRENT_VERSION + 1));
+ ServerStatsEncoding.parseBytes(newSerialized);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/common/ServerStatsFieldEnumsTest.java b/api/src/test/java/io/opencensus/common/ServerStatsFieldEnumsTest.java
new file mode 100644
index 00000000..ed786f6c
--- /dev/null
+++ b/api/src/test/java/io/opencensus/common/ServerStatsFieldEnumsTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.ServerStatsFieldEnums.Id;
+import io.opencensus.common.ServerStatsFieldEnums.Size;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ServerStatsFieldEnums}. */
+@RunWith(JUnit4.class)
+public class ServerStatsFieldEnumsTest {
+
+ @Test
+ public void enumIdValueOfTest() {
+ assertThat(Id.valueOf(0)).isEqualTo(Id.SERVER_STATS_LB_LATENCY_ID);
+ assertThat(Id.valueOf(1)).isEqualTo(Id.SERVER_STATS_SERVICE_LATENCY_ID);
+ assertThat(Id.valueOf(2)).isEqualTo(Id.SERVER_STATS_TRACE_OPTION_ID);
+ }
+
+ @Test
+ public void enumIdInvalidValueOfTest() {
+ assertThat(Id.valueOf(-1)).isNull();
+ assertThat(Id.valueOf(Id.values().length)).isNull();
+ assertThat(Id.valueOf(Id.values().length + 1)).isNull();
+ }
+
+ @Test
+ public void enumSizeValueTest() {
+ assertThat(Size.SERVER_STATS_LB_LATENCY_SIZE.value()).isEqualTo(8);
+ assertThat(Size.SERVER_STATS_SERVICE_LATENCY_SIZE.value()).isEqualTo(8);
+ assertThat(Size.SERVER_STATS_TRACE_OPTION_SIZE.value()).isEqualTo(1);
+ }
+
+ @Test
+ public void totalSizeTest() {
+ assertThat(ServerStatsFieldEnums.getTotalSize()).isEqualTo(20);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/common/ServerStatsTest.java b/api/src/test/java/io/opencensus/common/ServerStatsTest.java
new file mode 100644
index 00000000..620bbb4f
--- /dev/null
+++ b/api/src/test/java/io/opencensus/common/ServerStatsTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ServerStats}. */
+@RunWith(JUnit4.class)
+public class ServerStatsTest {
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void serverStatsCreate() {
+ ServerStats serverStats = null;
+
+ serverStats = ServerStats.create(31, 22, (byte) 0);
+ assertThat(serverStats.getLbLatencyNs()).isEqualTo(31);
+ assertThat(serverStats.getServiceLatencyNs()).isEqualTo(22);
+ assertThat(serverStats.getTraceOption()).isEqualTo((byte) 0);
+
+ serverStats = ServerStats.create(1000011L, 900022L, (byte) 1);
+ assertThat(serverStats.getLbLatencyNs()).isEqualTo(1000011L);
+ assertThat(serverStats.getServiceLatencyNs()).isEqualTo(900022L);
+ assertThat(serverStats.getTraceOption()).isEqualTo((byte) 1);
+
+ serverStats = ServerStats.create(0, 22, (byte) 0);
+ assertThat(serverStats.getLbLatencyNs()).isEqualTo(0);
+ assertThat(serverStats.getServiceLatencyNs()).isEqualTo(22);
+ assertThat(serverStats.getTraceOption()).isEqualTo((byte) 0);
+
+ serverStats = ServerStats.create(1010, 0, (byte) 0);
+ assertThat(serverStats.getLbLatencyNs()).isEqualTo(1010);
+ assertThat(serverStats.getServiceLatencyNs()).isEqualTo(0);
+ assertThat(serverStats.getTraceOption()).isEqualTo((byte) 0);
+ }
+
+ @Test
+ public void create_LbLatencyNegative() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'getLbLatencyNs' is less than zero");
+ ServerStats.create(-1L, 100, (byte) 0);
+ }
+
+ @Test
+ public void create_ServerLatencyNegative() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'getServiceLatencyNs' is less than zero");
+ ServerStats.create(100L, -1L, (byte) 0);
+ }
+
+ @Test
+ public void create_LbLatencyAndServerLatencyNegative() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'getLbLatencyNs' is less than zero");
+ ServerStats.create(-100L, -1L, (byte) 0);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/common/TimeUtilsTest.java b/api/src/test/java/io/opencensus/common/TimeUtilsTest.java
new file mode 100644
index 00000000..d6228566
--- /dev/null
+++ b/api/src/test/java/io/opencensus/common/TimeUtilsTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TimeUtils}. */
+@RunWith(JUnit4.class)
+public final class TimeUtilsTest {
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void compareLongs() {
+ assertThat(TimeUtils.compareLongs(-1L, 1L)).isLessThan(0);
+ assertThat(TimeUtils.compareLongs(10L, 10L)).isEqualTo(0);
+ assertThat(TimeUtils.compareLongs(1L, 0L)).isGreaterThan(0);
+ }
+
+ @Test
+ public void checkedAdd_TooLow() {
+ thrown.expect(ArithmeticException.class);
+ thrown.expectMessage("Long sum overflow: x=-9223372036854775807, y=-2");
+ TimeUtils.checkedAdd(Long.MIN_VALUE + 1, -2);
+ }
+
+ @Test
+ public void checkedAdd_TooHigh() {
+ thrown.expect(ArithmeticException.class);
+ thrown.expectMessage("Long sum overflow: x=9223372036854775806, y=2");
+ TimeUtils.checkedAdd(Long.MAX_VALUE - 1, 2);
+ }
+
+ @Test
+ public void checkedAdd_Valid() {
+ assertThat(TimeUtils.checkedAdd(1, 2)).isEqualTo(3);
+ assertThat(TimeUtils.checkedAdd(Integer.MAX_VALUE, Integer.MAX_VALUE))
+ .isEqualTo(2L * Integer.MAX_VALUE);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/common/TimestampTest.java b/api/src/test/java/io/opencensus/common/TimestampTest.java
new file mode 100644
index 00000000..b193b3c8
--- /dev/null
+++ b/api/src/test/java/io/opencensus/common/TimestampTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2017, 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Timestamp}. */
+@RunWith(JUnit4.class)
+public class TimestampTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void timestampCreate() {
+ assertThat(Timestamp.create(24, 42).getSeconds()).isEqualTo(24);
+ assertThat(Timestamp.create(24, 42).getNanos()).isEqualTo(42);
+ assertThat(Timestamp.create(-24, 42).getSeconds()).isEqualTo(-24);
+ assertThat(Timestamp.create(-24, 42).getNanos()).isEqualTo(42);
+ assertThat(Timestamp.create(315576000000L, 999999999).getSeconds()).isEqualTo(315576000000L);
+ assertThat(Timestamp.create(315576000000L, 999999999).getNanos()).isEqualTo(999999999);
+ assertThat(Timestamp.create(-315576000000L, 999999999).getSeconds()).isEqualTo(-315576000000L);
+ assertThat(Timestamp.create(-315576000000L, 999999999).getNanos()).isEqualTo(999999999);
+ }
+
+ @Test
+ public void create_SecondsTooLow() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is less than minimum (-315576000000): -315576000001");
+ Timestamp.create(-315576000001L, 0);
+ }
+
+ @Test
+ public void create_SecondsTooHigh() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is greater than maximum (315576000000): 315576000001");
+ Timestamp.create(315576000001L, 0);
+ }
+
+ @Test
+ public void create_NanosTooLow_PositiveTime() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'nanos' is less than zero: -1");
+ Timestamp.create(1, -1);
+ }
+
+ @Test
+ public void create_NanosTooHigh_PositiveTime() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'nanos' is greater than maximum (999999999): 1000000000");
+ Timestamp.create(1, 1000000000);
+ }
+
+ @Test
+ public void create_NanosTooLow_NegativeTime() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'nanos' is less than zero: -1");
+ Timestamp.create(-1, -1);
+ }
+
+ @Test
+ public void create_NanosTooHigh_NegativeTime() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'nanos' is greater than maximum (999999999): 1000000000");
+ Timestamp.create(-1, 1000000000);
+ }
+
+ @Test
+ public void timestampFromMillis() {
+ assertThat(Timestamp.fromMillis(0)).isEqualTo(Timestamp.create(0, 0));
+ assertThat(Timestamp.fromMillis(987)).isEqualTo(Timestamp.create(0, 987000000));
+ assertThat(Timestamp.fromMillis(3456)).isEqualTo(Timestamp.create(3, 456000000));
+ }
+
+ @Test
+ public void timestampFromMillis_Negative() {
+ assertThat(Timestamp.fromMillis(-1)).isEqualTo(Timestamp.create(-1, 999000000));
+ assertThat(Timestamp.fromMillis(-999)).isEqualTo(Timestamp.create(-1, 1000000));
+ assertThat(Timestamp.fromMillis(-3456)).isEqualTo(Timestamp.create(-4, 544000000));
+ }
+
+ @Test
+ public void fromMillis_TooLow() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is less than minimum (-315576000000): -315576000001");
+ Timestamp.fromMillis(-315576000001000L);
+ }
+
+ @Test
+ public void fromMillis_TooHigh() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("'seconds' is greater than maximum (315576000000): 315576000001");
+ Timestamp.fromMillis(315576000001000L);
+ }
+
+ @Test
+ public void timestampAddNanos() {
+ Timestamp timestamp = Timestamp.create(1234, 223);
+ assertThat(timestamp.addNanos(0)).isEqualTo(timestamp);
+ assertThat(timestamp.addNanos(999999777)).isEqualTo(Timestamp.create(1235, 0));
+ assertThat(timestamp.addNanos(1300200500)).isEqualTo(Timestamp.create(1235, 300200723));
+ assertThat(timestamp.addNanos(1999999777)).isEqualTo(Timestamp.create(1236, 0));
+ assertThat(timestamp.addNanos(9876543789L)).isEqualTo(Timestamp.create(1243, 876544012));
+ assertThat(timestamp.addNanos(Long.MAX_VALUE))
+ .isEqualTo(Timestamp.create(1234L + 9223372036L, 223 + 854775807));
+ }
+
+ @Test
+ public void timestampAddNanos_Negative() {
+ Timestamp timestamp = Timestamp.create(1234, 223);
+ assertThat(timestamp.addNanos(-223)).isEqualTo(Timestamp.create(1234, 0));
+ assertThat(timestamp.addNanos(-1000000223)).isEqualTo(Timestamp.create(1233, 0));
+ assertThat(timestamp.addNanos(-1300200500)).isEqualTo(Timestamp.create(1232, 699799723));
+ assertThat(timestamp.addNanos(-4123456213L)).isEqualTo(Timestamp.create(1229, 876544010));
+ assertThat(timestamp.addNanos(Long.MIN_VALUE))
+ .isEqualTo(Timestamp.create(1234L - 9223372036L - 1, 223 + 145224192));
+ }
+
+ @Test
+ public void timestampAddDuration() {
+ Timestamp timestamp = Timestamp.create(1234, 223);
+ assertThat(timestamp.addDuration(Duration.create(1, 0))).isEqualTo(Timestamp.create(1235, 223));
+ assertThat(timestamp.addDuration(Duration.create(0, 1))).isEqualTo(Timestamp.create(1234, 224));
+ assertThat(timestamp.addDuration(Duration.create(1, 1))).isEqualTo(Timestamp.create(1235, 224));
+ assertThat(timestamp.addDuration(Duration.create(1, 999999900)))
+ .isEqualTo(Timestamp.create(1236, 123));
+ }
+
+ @Test
+ public void timestampAddDuration_Negative() {
+ Timestamp timestamp = Timestamp.create(1234, 223);
+ assertThat(timestamp.addDuration(Duration.create(-1234, -223)))
+ .isEqualTo(Timestamp.create(0, 0));
+ assertThat(timestamp.addDuration(Duration.create(-1, 0)))
+ .isEqualTo(Timestamp.create(1233, 223));
+ assertThat(timestamp.addDuration(Duration.create(-1, -1)))
+ .isEqualTo(Timestamp.create(1233, 222));
+ assertThat(timestamp.addDuration(Duration.create(-1, -323)))
+ .isEqualTo(Timestamp.create(1232, 999999900));
+ assertThat(timestamp.addDuration(Duration.create(-33, -999999999)))
+ .isEqualTo(Timestamp.create(1200, 224));
+ }
+
+ @Test
+ public void timestampSubtractTimestamp() {
+ Timestamp timestamp = Timestamp.create(1234, 223);
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(0, 0)))
+ .isEqualTo(Duration.create(1234, 223));
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1233, 223)))
+ .isEqualTo(Duration.create(1, 0));
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1233, 222)))
+ .isEqualTo(Duration.create(1, 1));
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1232, 999999900)))
+ .isEqualTo(Duration.create(1, 323));
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1200, 224)))
+ .isEqualTo(Duration.create(33, 999999999));
+ }
+
+ @Test
+ public void timestampSubtractTimestamp_NegativeResult() {
+ Timestamp timestamp = Timestamp.create(1234, 223);
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1235, 223)))
+ .isEqualTo(Duration.create(-1, 0));
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1234, 224)))
+ .isEqualTo(Duration.create(0, -1));
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1235, 224)))
+ .isEqualTo(Duration.create(-1, -1));
+ assertThat(timestamp.subtractTimestamp(Timestamp.create(1236, 123)))
+ .isEqualTo(Duration.create(-1, -999999900));
+ }
+
+ @Test
+ public void timestamp_CompareTo() {
+ assertThat(Timestamp.create(0, 0).compareTo(Timestamp.create(0, 0))).isEqualTo(0);
+ assertThat(Timestamp.create(24, 42).compareTo(Timestamp.create(24, 42))).isEqualTo(0);
+ assertThat(Timestamp.create(-24, 42).compareTo(Timestamp.create(-24, 42))).isEqualTo(0);
+ assertThat(Timestamp.create(25, 42).compareTo(Timestamp.create(24, 42))).isEqualTo(1);
+ assertThat(Timestamp.create(24, 45).compareTo(Timestamp.create(24, 42))).isEqualTo(1);
+ assertThat(Timestamp.create(24, 42).compareTo(Timestamp.create(25, 42))).isEqualTo(-1);
+ assertThat(Timestamp.create(24, 42).compareTo(Timestamp.create(24, 45))).isEqualTo(-1);
+ assertThat(Timestamp.create(-25, 42).compareTo(Timestamp.create(-24, 42))).isEqualTo(-1);
+ assertThat(Timestamp.create(-24, 45).compareTo(Timestamp.create(-24, 42))).isEqualTo(1);
+ assertThat(Timestamp.create(-24, 42).compareTo(Timestamp.create(-25, 42))).isEqualTo(1);
+ assertThat(Timestamp.create(-24, 42).compareTo(Timestamp.create(-24, 45))).isEqualTo(-1);
+ }
+
+ @Test
+ public void timestamp_Equal() {
+ // Positive tests.
+ assertThat(Timestamp.create(0, 0)).isEqualTo(Timestamp.create(0, 0));
+ assertThat(Timestamp.create(24, 42)).isEqualTo(Timestamp.create(24, 42));
+ assertThat(Timestamp.create(-24, 42)).isEqualTo(Timestamp.create(-24, 42));
+ // Negative tests.
+ assertThat(Timestamp.create(25, 42)).isNotEqualTo(Timestamp.create(24, 42));
+ assertThat(Timestamp.create(24, 43)).isNotEqualTo(Timestamp.create(24, 42));
+ assertThat(Timestamp.create(-25, 42)).isNotEqualTo(Timestamp.create(-24, 42));
+ assertThat(Timestamp.create(-24, 43)).isNotEqualTo(Timestamp.create(-24, 42));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/internal/ProviderTest.java b/api/src/test/java/io/opencensus/internal/ProviderTest.java
new file mode 100644
index 00000000..1f4c33fa
--- /dev/null
+++ b/api/src/test/java/io/opencensus/internal/ProviderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016-17, 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.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.ServiceConfigurationError;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Provider}. */
+@RunWith(JUnit4.class)
+public class ProviderTest {
+ static class GoodClass {
+ public GoodClass() {}
+ }
+
+ static class PrivateConstructorClass {
+ private PrivateConstructorClass() {}
+ }
+
+ static class NoDefaultConstructorClass {
+ public NoDefaultConstructorClass(int arg) {}
+ }
+
+ private static class PrivateClass {}
+
+ static interface MyInterface {}
+
+ static class MyInterfaceImpl implements MyInterface {
+ public MyInterfaceImpl() {}
+ }
+
+ @Test(expected = ServiceConfigurationError.class)
+ public void createInstance_ThrowsErrorWhenClassIsPrivate() throws ClassNotFoundException {
+ Provider.createInstance(
+ Class.forName(
+ "io.opencensus.internal.ProviderTest$PrivateClass",
+ true,
+ ProviderTest.class.getClassLoader()),
+ PrivateClass.class);
+ }
+
+ @Test(expected = ServiceConfigurationError.class)
+ public void createInstance_ThrowsErrorWhenClassHasPrivateConstructor()
+ throws ClassNotFoundException {
+ Provider.createInstance(
+ Class.forName(
+ "io.opencensus.internal.ProviderTest$PrivateConstructorClass",
+ true,
+ ProviderTest.class.getClassLoader()),
+ PrivateConstructorClass.class);
+ }
+
+ @Test(expected = ServiceConfigurationError.class)
+ public void createInstance_ThrowsErrorWhenClassDoesNotHaveDefaultConstructor()
+ throws ClassNotFoundException {
+ Provider.createInstance(
+ Class.forName(
+ "io.opencensus.internal.ProviderTest$NoDefaultConstructorClass",
+ true,
+ ProviderTest.class.getClassLoader()),
+ NoDefaultConstructorClass.class);
+ }
+
+ @Test(expected = ServiceConfigurationError.class)
+ public void createInstance_ThrowsErrorWhenClassIsNotASubclass() throws ClassNotFoundException {
+ Provider.createInstance(
+ Class.forName(
+ "io.opencensus.internal.ProviderTest$GoodClass",
+ true,
+ ProviderTest.class.getClassLoader()),
+ MyInterface.class);
+ }
+
+ @Test
+ public void createInstance_GoodClass() throws ClassNotFoundException {
+ assertThat(
+ Provider.createInstance(
+ Class.forName(
+ "io.opencensus.internal.ProviderTest$GoodClass",
+ true,
+ ProviderTest.class.getClassLoader()),
+ GoodClass.class))
+ .isNotNull();
+ }
+
+ @Test
+ public void createInstance_GoodSubclass() throws ClassNotFoundException {
+ assertThat(
+ Provider.createInstance(
+ Class.forName(
+ "io.opencensus.internal.ProviderTest$MyInterfaceImpl",
+ true,
+ ProviderTest.class.getClassLoader()),
+ MyInterface.class))
+ .isNotNull();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/internal/StringUtilsTest.java b/api/src/test/java/io/opencensus/internal/StringUtilsTest.java
new file mode 100644
index 00000000..5e866940
--- /dev/null
+++ b/api/src/test/java/io/opencensus/internal/StringUtilsTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016-17, 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.internal;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link StringUtils}. */
+@RunWith(JUnit4.class)
+public final class StringUtilsTest {
+
+ @Test
+ public void isPrintableString() {
+ assertTrue(StringUtils.isPrintableString("abcd"));
+ assertFalse(StringUtils.isPrintableString("\2ab\3cd"));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/internal/UtilsTest.java b/api/src/test/java/io/opencensus/internal/UtilsTest.java
new file mode 100644
index 00000000..608a8fe0
--- /dev/null
+++ b/api/src/test/java/io/opencensus/internal/UtilsTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.internal;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Utils}. */
+@RunWith(JUnit4.class)
+public final class UtilsTest {
+ private static final String TEST_MESSAGE = "test message";
+ private static final String TEST_MESSAGE_TEMPLATE = "I ate %s eggs.";
+ private static final int TEST_MESSAGE_VALUE = 2;
+ private static final String FORMATED_SIMPLE_TEST_MESSAGE = "I ate 2 eggs.";
+ private static final String FORMATED_COMPLEX_TEST_MESSAGE = "I ate 2 eggs. [2]";
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void checkArgument() {
+ Utils.checkArgument(true, TEST_MESSAGE);
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage(TEST_MESSAGE);
+ Utils.checkArgument(false, TEST_MESSAGE);
+ }
+
+ @Test
+ public void checkArgument_NullErrorMessage() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("null");
+ Utils.checkArgument(false, null);
+ }
+
+ @Test
+ public void checkArgument_WithSimpleFormat() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage(FORMATED_SIMPLE_TEST_MESSAGE);
+ Utils.checkArgument(false, TEST_MESSAGE_TEMPLATE, TEST_MESSAGE_VALUE);
+ }
+
+ @Test
+ public void checkArgument_WithComplexFormat() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage(FORMATED_COMPLEX_TEST_MESSAGE);
+ Utils.checkArgument(false, TEST_MESSAGE_TEMPLATE, TEST_MESSAGE_VALUE, TEST_MESSAGE_VALUE);
+ }
+
+ @Test
+ public void checkState() {
+ Utils.checkNotNull(true, TEST_MESSAGE);
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage(TEST_MESSAGE);
+ Utils.checkState(false, TEST_MESSAGE);
+ }
+
+ @Test
+ public void checkState_NullErrorMessage() {
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("null");
+ Utils.checkState(false, null);
+ }
+
+ @Test
+ public void checkNotNull() {
+ Utils.checkNotNull(new Object(), TEST_MESSAGE);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(TEST_MESSAGE);
+ Utils.checkNotNull(null, TEST_MESSAGE);
+ }
+
+ @Test
+ public void checkNotNull_NullErrorMessage() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("null");
+ Utils.checkNotNull(null, null);
+ }
+
+ @Test
+ public void checkIndex_Valid() {
+ Utils.checkIndex(1, 2);
+ }
+
+ @Test
+ public void checkIndex_NegativeSize() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Negative size: -1");
+ Utils.checkIndex(0, -1);
+ }
+
+ @Test
+ public void checkIndex_NegativeIndex() {
+ thrown.expect(IndexOutOfBoundsException.class);
+ thrown.expectMessage("Index out of bounds: size=10, index=-2");
+ Utils.checkIndex(-2, 10);
+ }
+
+ @Test
+ public void checkIndex_IndexEqualToSize() {
+ thrown.expect(IndexOutOfBoundsException.class);
+ thrown.expectMessage("Index out of bounds: size=5, index=5");
+ Utils.checkIndex(5, 5);
+ }
+
+ @Test
+ public void checkIndex_IndexGreaterThanSize() {
+ thrown.expect(IndexOutOfBoundsException.class);
+ thrown.expectMessage("Index out of bounds: size=10, index=11");
+ Utils.checkIndex(11, 10);
+ }
+
+ @Test
+ public void equalsObjects_Equal() {
+ assertTrue(Utils.equalsObjects(null, null));
+ assertTrue(Utils.equalsObjects(new Date(1L), new Date(1L)));
+ }
+
+ @Test
+ public void equalsObjects_Unequal() {
+ assertFalse(Utils.equalsObjects(null, new Object()));
+ assertFalse(Utils.equalsObjects(new Object(), null));
+ assertFalse(Utils.equalsObjects(new Object(), new Object()));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/DerivedDoubleGaugeTest.java b/api/src/test/java/io/opencensus/metrics/DerivedDoubleGaugeTest.java
new file mode 100644
index 00000000..dbae3c49
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/DerivedDoubleGaugeTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.common.ToDoubleFunction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link DerivedDoubleGauge}. */
+// TODO(mayurkale): Add more tests, once DerivedDoubleGauge plugs-in into the registry.
+@RunWith(JUnit4.class)
+public class DerivedDoubleGaugeTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ private static final String NAME = "name";
+ private static final String DESCRIPTION = "description";
+ private static final String UNIT = "1";
+ private static final List<LabelKey> LABEL_KEY =
+ Collections.singletonList(LabelKey.create("key", "key description"));
+ private static final List<LabelValue> LABEL_VALUES =
+ Collections.singletonList(LabelValue.create("value"));
+ private static final List<LabelValue> EMPTY_LABEL_VALUES = new ArrayList<LabelValue>();
+
+ private final DerivedDoubleGauge derivedDoubleGauge =
+ DerivedDoubleGauge.newNoopDerivedDoubleGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ private static final ToDoubleFunction<Object> doubleFunction =
+ new ToDoubleFunction<Object>() {
+ @Override
+ public double applyAsDouble(Object value) {
+ return 5.0;
+ }
+ };
+
+ @Test
+ public void noopCreateTimeSeries_WithNullLabelValues() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ derivedDoubleGauge.createTimeSeries(null, null, doubleFunction);
+ }
+
+ @Test
+ public void noopCreateTimeSeries_WithNullElement() {
+ List<LabelValue> labelValues = Collections.singletonList(null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValue element should not be null.");
+ derivedDoubleGauge.createTimeSeries(labelValues, null, doubleFunction);
+ }
+
+ @Test
+ public void noopCreateTimeSeries_WithInvalidLabelSize() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Incorrect number of labels.");
+ derivedDoubleGauge.createTimeSeries(EMPTY_LABEL_VALUES, null, doubleFunction);
+ }
+
+ @Test
+ public void createTimeSeries_WithNullFunction() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("function");
+ derivedDoubleGauge.createTimeSeries(LABEL_VALUES, null, null);
+ }
+
+ @Test
+ public void noopRemoveTimeSeries_WithNullLabelValues() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ derivedDoubleGauge.removeTimeSeries(null);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/DerivedLongGaugeTest.java b/api/src/test/java/io/opencensus/metrics/DerivedLongGaugeTest.java
new file mode 100644
index 00000000..6a462881
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/DerivedLongGaugeTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.metrics;
+
+import io.opencensus.common.ToLongFunction;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link DerivedLongGauge}. */
+// TODO(mayurkale): Add more tests, once DerivedLongGauge plugs-in into the registry.
+@RunWith(JUnit4.class)
+public class DerivedLongGaugeTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ private static final String NAME = "name";
+ private static final String DESCRIPTION = "description";
+ private static final String UNIT = "1";
+ private static final List<LabelKey> LABEL_KEY =
+ Collections.singletonList(LabelKey.create("key", "key description"));
+ private static final List<LabelValue> LABEL_VALUES =
+ Collections.singletonList(LabelValue.create("value"));
+ private static final List<LabelValue> EMPTY_LABEL_VALUES = new ArrayList<LabelValue>();
+
+ private final DerivedLongGauge derivedLongGauge =
+ DerivedLongGauge.newNoopDerivedLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ private static final ToLongFunction<Object> longFunction =
+ new ToLongFunction<Object>() {
+ @Override
+ public long applyAsLong(Object value) {
+ return 5;
+ }
+ };
+
+ @Test
+ public void noopCreateTimeSeries_WithNullLabelValues() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ derivedLongGauge.createTimeSeries(null, null, longFunction);
+ }
+
+ @Test
+ public void noopCreateTimeSeries_WithNullElement() {
+ List<LabelValue> labelValues = Collections.singletonList(null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValue element should not be null.");
+ derivedLongGauge.createTimeSeries(labelValues, null, longFunction);
+ }
+
+ @Test
+ public void noopCreateTimeSeries_WithInvalidLabelSize() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Incorrect number of labels.");
+ derivedLongGauge.createTimeSeries(EMPTY_LABEL_VALUES, null, longFunction);
+ }
+
+ @Test
+ public void createTimeSeries_WithNullFunction() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("function");
+ derivedLongGauge.createTimeSeries(LABEL_VALUES, null, null);
+ }
+
+ @Test
+ public void noopRemoveTimeSeries_WithNullLabelValues() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ derivedLongGauge.removeTimeSeries(null);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/DoubleGaugeTest.java b/api/src/test/java/io/opencensus/metrics/DoubleGaugeTest.java
new file mode 100644
index 00000000..b0cdea7c
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/DoubleGaugeTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.metrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link DoubleGauge}. */
+@RunWith(JUnit4.class)
+public class DoubleGaugeTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ private static final String NAME = "name";
+ private static final String DESCRIPTION = "description";
+ private static final String UNIT = "1";
+ private static final List<LabelKey> LABEL_KEY =
+ Collections.singletonList(LabelKey.create("key", "key description"));
+ private static final List<LabelValue> LABEL_VALUES =
+ Collections.singletonList(LabelValue.create("value"));
+ private static final List<LabelKey> EMPTY_LABEL_KEYS = new ArrayList<LabelKey>();
+ private static final List<LabelValue> EMPTY_LABEL_VALUES = new ArrayList<LabelValue>();
+
+ // TODO(mayurkale): Add more tests, once DoubleGauge plugs-in into the registry.
+
+ @Test
+ public void noopGetOrCreateTimeSeries_WithNullLabelValues() {
+ DoubleGauge doubleGauge =
+ DoubleGauge.newNoopDoubleGauge(NAME, DESCRIPTION, UNIT, EMPTY_LABEL_KEYS);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ doubleGauge.getOrCreateTimeSeries(null);
+ }
+
+ @Test
+ public void noopGetOrCreateTimeSeries_WithNullElement() {
+ List<LabelValue> labelValues = Collections.singletonList(null);
+ DoubleGauge doubleGauge = DoubleGauge.newNoopDoubleGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValue element should not be null.");
+ doubleGauge.getOrCreateTimeSeries(labelValues);
+ }
+
+ @Test
+ public void noopGetOrCreateTimeSeries_WithInvalidLabelSize() {
+ DoubleGauge doubleGauge = DoubleGauge.newNoopDoubleGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Incorrect number of labels.");
+ doubleGauge.getOrCreateTimeSeries(EMPTY_LABEL_VALUES);
+ }
+
+ @Test
+ public void noopRemoveTimeSeries_WithNullLabelValues() {
+ DoubleGauge doubleGauge = DoubleGauge.newNoopDoubleGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ doubleGauge.removeTimeSeries(null);
+ }
+
+ @Test
+ public void noopSameAs() {
+ DoubleGauge doubleGauge = DoubleGauge.newNoopDoubleGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ assertThat(doubleGauge.getDefaultTimeSeries()).isSameAs(doubleGauge.getDefaultTimeSeries());
+ assertThat(doubleGauge.getDefaultTimeSeries())
+ .isSameAs(doubleGauge.getOrCreateTimeSeries(LABEL_VALUES));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/LabelKeyTest.java b/api/src/test/java/io/opencensus/metrics/LabelKeyTest.java
new file mode 100644
index 00000000..83f2b59a
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/LabelKeyTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.metrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link LabelKey}. */
+@RunWith(JUnit4.class)
+public class LabelKeyTest {
+
+ private static final LabelKey KEY = LabelKey.create("key", "description");
+
+ @Test
+ public void testGetKey() {
+ assertThat(KEY.getKey()).isEqualTo("key");
+ }
+
+ @Test
+ public void testGetDescription() {
+ assertThat(KEY.getDescription()).isEqualTo("description");
+ }
+
+ @Test
+ public void create_NoLengthConstraint() {
+ // We have a length constraint of 256-characters for TagKey. That constraint doesn't apply to
+ // LabelKey.
+ char[] chars = new char[300];
+ Arrays.fill(chars, 'k');
+ String key = new String(chars);
+ assertThat(LabelKey.create(key, "").getKey()).isEqualTo(key);
+ }
+
+ @Test
+ public void create_WithUnprintableChars() {
+ String key = "\2ab\3cd";
+ String description = "\4ef\5gh";
+ LabelKey labelKey = LabelKey.create(key, description);
+ assertThat(labelKey.getKey()).isEqualTo(key);
+ assertThat(labelKey.getDescription()).isEqualTo(description);
+ }
+
+ @Test
+ public void create_WithNonAsciiChars() {
+ String key = "键";
+ String description = "测试用键";
+ LabelKey nonAsciiKey = LabelKey.create(key, description);
+ assertThat(nonAsciiKey.getKey()).isEqualTo(key);
+ assertThat(nonAsciiKey.getDescription()).isEqualTo(description);
+ }
+
+ @Test
+ public void create_Empty() {
+ LabelKey emptyKey = LabelKey.create("", "");
+ assertThat(emptyKey.getKey()).isEmpty();
+ assertThat(emptyKey.getDescription()).isEmpty();
+ }
+
+ @Test
+ public void testLabelKeyEquals() {
+ new EqualsTester()
+ .addEqualityGroup(LabelKey.create("foo", ""), LabelKey.create("foo", ""))
+ .addEqualityGroup(LabelKey.create("foo", "description"))
+ .addEqualityGroup(LabelKey.create("bar", ""))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/LabelValueTest.java b/api/src/test/java/io/opencensus/metrics/LabelValueTest.java
new file mode 100644
index 00000000..e5526b2f
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/LabelValueTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.metrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link LabelValue}. */
+@RunWith(JUnit4.class)
+public class LabelValueTest {
+
+ private static final LabelValue VALUE = LabelValue.create("value");
+ private static final LabelValue UNSET = LabelValue.create(null);
+ private static final LabelValue EMPTY = LabelValue.create("");
+
+ @Test
+ public void testGetValue() {
+ assertThat(VALUE.getValue()).isEqualTo("value");
+ assertThat(UNSET.getValue()).isNull();
+ assertThat(EMPTY.getValue()).isEmpty();
+ }
+
+ @Test
+ public void create_NoLengthConstraint() {
+ // We have a length constraint of 256-characters for TagValue. That constraint doesn't apply to
+ // LabelValue.
+ char[] chars = new char[300];
+ Arrays.fill(chars, 'v');
+ String value = new String(chars);
+ assertThat(LabelValue.create(value).getValue()).isEqualTo(value);
+ }
+
+ @Test
+ public void create_WithUnprintableChars() {
+ String value = "\2ab\3cd";
+ assertThat(LabelValue.create(value).getValue()).isEqualTo(value);
+ }
+
+ @Test
+ public void create_WithNonAsciiChars() {
+ String value = "值";
+ LabelValue nonAsciiValue = LabelValue.create(value);
+ assertThat(nonAsciiValue.getValue()).isEqualTo(value);
+ }
+
+ @Test
+ public void testLabelValueEquals() {
+ new EqualsTester()
+ .addEqualityGroup(LabelValue.create("foo"), LabelValue.create("foo"))
+ .addEqualityGroup(UNSET)
+ .addEqualityGroup(EMPTY)
+ .addEqualityGroup(LabelValue.create("bar"))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/LongGaugeTest.java b/api/src/test/java/io/opencensus/metrics/LongGaugeTest.java
new file mode 100644
index 00000000..eedb287c
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/LongGaugeTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.metrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link LongGauge}. */
+@RunWith(JUnit4.class)
+public class LongGaugeTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ private static final String NAME = "name";
+ private static final String DESCRIPTION = "description";
+ private static final String UNIT = "1";
+ private static final List<LabelKey> LABEL_KEY =
+ Collections.singletonList(LabelKey.create("key", "key description"));
+ private static final List<LabelValue> LABEL_VALUES =
+ Collections.singletonList(LabelValue.create("value"));
+ private static final List<LabelKey> EMPTY_LABEL_KEYS = new ArrayList<LabelKey>();
+ private static final List<LabelValue> EMPTY_LABEL_VALUES = new ArrayList<LabelValue>();
+
+ // TODO(mayurkale): Add more tests, once LongGauge plugs-in into the registry.
+
+ @Test
+ public void noopGetOrCreateTimeSeries_WithNullLabelValues() {
+ LongGauge longGauge = LongGauge.newNoopLongGauge(NAME, DESCRIPTION, UNIT, EMPTY_LABEL_KEYS);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ longGauge.getOrCreateTimeSeries(null);
+ }
+
+ @Test
+ public void noopGetOrCreateTimeSeries_WithNullElement() {
+ List<LabelValue> labelValues = Collections.singletonList(null);
+ LongGauge longGauge = LongGauge.newNoopLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValue element should not be null.");
+ longGauge.getOrCreateTimeSeries(labelValues);
+ }
+
+ @Test
+ public void noopGetOrCreateTimeSeries_WithInvalidLabelSize() {
+ LongGauge longGauge = LongGauge.newNoopLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Incorrect number of labels.");
+ longGauge.getOrCreateTimeSeries(EMPTY_LABEL_VALUES);
+ }
+
+ @Test
+ public void noopRemoveTimeSeries_WithNullLabelValues() {
+ LongGauge longGauge = LongGauge.newNoopLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelValues");
+ longGauge.removeTimeSeries(null);
+ }
+
+ @Test
+ public void noopSameAs() {
+ LongGauge longGauge = LongGauge.newNoopLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ assertThat(longGauge.getDefaultTimeSeries()).isSameAs(longGauge.getDefaultTimeSeries());
+ assertThat(longGauge.getDefaultTimeSeries())
+ .isSameAs(longGauge.getOrCreateTimeSeries(LABEL_VALUES));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/MetricRegistryTest.java b/api/src/test/java/io/opencensus/metrics/MetricRegistryTest.java
new file mode 100644
index 00000000..d8a26cc8
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/MetricRegistryTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.metrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link MetricRegistry}. */
+@RunWith(JUnit4.class)
+public class MetricRegistryTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ private static final String NAME = "name";
+ private static final String NAME_2 = "name2";
+ private static final String NAME_3 = "name3";
+ private static final String NAME_4 = "name4";
+ private static final String DESCRIPTION = "description";
+ private static final String UNIT = "1";
+ private static final List<LabelKey> LABEL_KEY =
+ Collections.singletonList(LabelKey.create("key", "key description"));
+ private static final List<LabelValue> LABEL_VALUES =
+ Collections.singletonList(LabelValue.create("value"));
+ private final MetricRegistry metricRegistry =
+ MetricsComponent.newNoopMetricsComponent().getMetricRegistry();
+
+ @Test
+ public void noopAddLongGauge_NullName() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("name");
+ metricRegistry.addLongGauge(null, DESCRIPTION, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddLongGauge_NullDescription() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("description");
+ metricRegistry.addLongGauge(NAME, null, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddLongGauge_NullUnit() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("unit");
+ metricRegistry.addLongGauge(NAME, DESCRIPTION, null, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddLongGauge_NullLabels() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKeys");
+ metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, null);
+ }
+
+ @Test
+ public void noopAddLongGauge_WithNullElement() {
+ List<LabelKey> labelKeys = Collections.singletonList(null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKey element should not be null.");
+ metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, labelKeys);
+ }
+
+ @Test
+ public void noopAddDoubleGauge_NullName() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("name");
+ metricRegistry.addDoubleGauge(null, DESCRIPTION, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDoubleGauge_NullDescription() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("description");
+ metricRegistry.addDoubleGauge(NAME_2, null, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDoubleGauge_NullUnit() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("unit");
+ metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, null, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDoubleGauge_NullLabels() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKeys");
+ metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, null);
+ }
+
+ @Test
+ public void noopAddDoubleGauge_WithNullElement() {
+ List<LabelKey> labelKeys = Collections.singletonList(null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKey element should not be null.");
+ metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, labelKeys);
+ }
+
+ @Test
+ public void noopAddDerivedLongGauge_NullName() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("name");
+ metricRegistry.addDerivedLongGauge(null, DESCRIPTION, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDerivedLongGauge_NullDescription() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("description");
+ metricRegistry.addDerivedLongGauge(NAME_3, null, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDerivedLongGauge_NullUnit() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("unit");
+ metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, null, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDerivedLongGauge_NullLabels() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKeys");
+ metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, null);
+ }
+
+ @Test
+ public void noopAddDerivedLongGauge_WithNullElement() {
+ List<LabelKey> labelKeys = Collections.singletonList(null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKey element should not be null.");
+ metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, labelKeys);
+ }
+
+ @Test
+ public void noopAddDerivedDoubleGauge_NullName() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("name");
+ metricRegistry.addDerivedDoubleGauge(null, DESCRIPTION, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDerivedDoubleGauge_NullDescription() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("description");
+ metricRegistry.addDerivedDoubleGauge(NAME_4, null, UNIT, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDerivedDoubleGauge_NullUnit() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("unit");
+ metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, null, LABEL_KEY);
+ }
+
+ @Test
+ public void noopAddDerivedDoubleGauge_NullLabels() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKeys");
+ metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, null);
+ }
+
+ @Test
+ public void noopAddDerivedDoubleGauge_WithNullElement() {
+ List<LabelKey> labelKeys = Collections.singletonList(null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("labelKey element should not be null.");
+ metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, labelKeys);
+ }
+
+ @Test
+ public void noopSameAs() {
+ LongGauge longGauge = metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY);
+ assertThat(longGauge.getDefaultTimeSeries()).isSameAs(longGauge.getDefaultTimeSeries());
+ assertThat(longGauge.getDefaultTimeSeries())
+ .isSameAs(longGauge.getOrCreateTimeSeries(LABEL_VALUES));
+
+ DoubleGauge doubleGauge = metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, LABEL_KEY);
+ assertThat(doubleGauge.getDefaultTimeSeries()).isSameAs(doubleGauge.getDefaultTimeSeries());
+ assertThat(doubleGauge.getDefaultTimeSeries())
+ .isSameAs(doubleGauge.getOrCreateTimeSeries(LABEL_VALUES));
+ }
+
+ @Test
+ public void noopInstanceOf() {
+ assertThat(metricRegistry.addLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY))
+ .isInstanceOf(LongGauge.newNoopLongGauge(NAME, DESCRIPTION, UNIT, LABEL_KEY).getClass());
+ assertThat(metricRegistry.addDoubleGauge(NAME_2, DESCRIPTION, UNIT, LABEL_KEY))
+ .isInstanceOf(
+ DoubleGauge.newNoopDoubleGauge(NAME_2, DESCRIPTION, UNIT, LABEL_KEY).getClass());
+ assertThat(metricRegistry.addDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, LABEL_KEY))
+ .isInstanceOf(
+ DerivedLongGauge.newNoopDerivedLongGauge(NAME_3, DESCRIPTION, UNIT, LABEL_KEY)
+ .getClass());
+ assertThat(metricRegistry.addDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, LABEL_KEY))
+ .isInstanceOf(
+ DerivedDoubleGauge.newNoopDerivedDoubleGauge(NAME_4, DESCRIPTION, UNIT, LABEL_KEY)
+ .getClass());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/MetricsComponentTest.java b/api/src/test/java/io/opencensus/metrics/MetricsComponentTest.java
new file mode 100644
index 00000000..1c4e70f7
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/MetricsComponentTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.metrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.metrics.export.ExportComponent;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link MetricsComponent}. */
+@RunWith(JUnit4.class)
+public class MetricsComponentTest {
+ @Test
+ public void defaultExportComponent() {
+ assertThat(MetricsComponent.newNoopMetricsComponent().getExportComponent())
+ .isInstanceOf(ExportComponent.newNoopExportComponent().getClass());
+ }
+
+ @Test
+ public void defaultMetricRegistry() {
+ assertThat(MetricsComponent.newNoopMetricsComponent().getMetricRegistry())
+ .isInstanceOf(MetricRegistry.newNoopMetricRegistry().getClass());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/MetricsTest.java b/api/src/test/java/io/opencensus/metrics/MetricsTest.java
new file mode 100644
index 00000000..9e0eee1f
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/MetricsTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.metrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.metrics.export.ExportComponent;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Metrics}. */
+@RunWith(JUnit4.class)
+public class MetricsTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void loadMetricsComponent_UsesProvidedClassLoader() {
+ final RuntimeException toThrow = new RuntimeException("UseClassLoader");
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("UseClassLoader");
+ Metrics.loadMetricsComponent(
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) {
+ throw toThrow;
+ }
+ });
+ }
+
+ @Test
+ public void loadMetricsComponent_IgnoresMissingClasses() {
+ ClassLoader classLoader =
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException();
+ }
+ };
+ assertThat(Metrics.loadMetricsComponent(classLoader).getClass().getName())
+ .isEqualTo("io.opencensus.metrics.MetricsComponent$NoopMetricsComponent");
+ }
+
+ @Test
+ public void defaultExportComponent() {
+ assertThat(Metrics.getExportComponent())
+ .isInstanceOf(ExportComponent.newNoopExportComponent().getClass());
+ }
+
+ @Test
+ public void defaultMetricRegistry() {
+ assertThat(Metrics.getMetricRegistry())
+ .isInstanceOf(MetricRegistry.newNoopMetricRegistry().getClass());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/DistributionTest.java b/api/src/test/java/io/opencensus/metrics/export/DistributionTest.java
new file mode 100644
index 00000000..85b31498
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/DistributionTest.java
@@ -0,0 +1,331 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.metrics.export.Distribution.Bucket;
+import io.opencensus.metrics.export.Distribution.BucketOptions;
+import io.opencensus.metrics.export.Distribution.BucketOptions.ExplicitOptions;
+import io.opencensus.metrics.export.Distribution.Exemplar;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Distribution}. */
+@RunWith(JUnit4.class)
+public class DistributionTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ private static final Timestamp TIMESTAMP = Timestamp.create(1, 0);
+ private static final Map<String, String> ATTACHMENTS = Collections.singletonMap("key", "value");
+ private static final double TOLERANCE = 1e-6;
+
+ @Test
+ public void createAndGet_Bucket() {
+ Bucket bucket = Bucket.create(98);
+ assertThat(bucket.getCount()).isEqualTo(98);
+ assertThat(bucket.getExemplar()).isNull();
+ }
+
+ @Test
+ public void createAndGet_BucketWithExemplar() {
+ Exemplar exemplar = Exemplar.create(12.2, TIMESTAMP, ATTACHMENTS);
+ Bucket bucket = Bucket.create(7, exemplar);
+ assertThat(bucket.getCount()).isEqualTo(7);
+ assertThat(bucket.getExemplar()).isEqualTo(exemplar);
+ }
+
+ @Test
+ public void createBucket_preventNullExemplar() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("exemplar");
+ Bucket.create(1, null);
+ }
+
+ @Test
+ public void createAndGet_Exemplar() {
+ Exemplar exemplar = Exemplar.create(-9.9, TIMESTAMP, ATTACHMENTS);
+ assertThat(exemplar.getValue()).isWithin(TOLERANCE).of(-9.9);
+ assertThat(exemplar.getTimestamp()).isEqualTo(TIMESTAMP);
+ assertThat(exemplar.getAttachments()).isEqualTo(ATTACHMENTS);
+ }
+
+ @Test
+ public void createAndGet_ExplicitBuckets() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 3.0);
+
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+ final List<Double> actual = new ArrayList<Double>();
+ bucketOptions.match(
+ new Function<ExplicitOptions, Object>() {
+ @Override
+ public Object apply(ExplicitOptions arg) {
+ actual.addAll(arg.getBucketBoundaries());
+ return null;
+ }
+ },
+ Functions.throwAssertionError());
+
+ assertThat(actual).containsExactlyElementsIn(bucketBounds).inOrder();
+ }
+
+ @Test
+ public void createAndGet_ExplicitBucketsNegativeBounds() {
+ List<Double> bucketBounds = Collections.singletonList(-1.0);
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("bucket boundary should be > 0");
+ BucketOptions.explicitOptions(bucketBounds);
+ }
+
+ @Test
+ public void createAndGet_PreventNullExplicitBuckets() {
+ thrown.expect(NullPointerException.class);
+ BucketOptions.explicitOptions(Arrays.asList(1.0, null, 3.0));
+ }
+
+ @Test
+ public void createAndGet_ExplicitBucketsEmptyBounds() {
+ List<Double> bucketBounds = new ArrayList<Double>();
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+
+ final List<Double> actual = new ArrayList<Double>();
+ bucketOptions.match(
+ new Function<ExplicitOptions, Object>() {
+ @Override
+ public Object apply(ExplicitOptions arg) {
+ actual.addAll(arg.getBucketBoundaries());
+ return null;
+ }
+ },
+ Functions.throwAssertionError());
+
+ assertThat(actual).isEmpty();
+ }
+
+ @Test
+ public void createBucketOptions_UnorderedBucketBounds() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 5.0, 2.0);
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("bucket boundaries not sorted.");
+ BucketOptions.explicitOptions(bucketBounds);
+ }
+
+ @Test
+ public void createAndGet_PreventNullBucketOptions() {
+ thrown.expect(NullPointerException.class);
+ BucketOptions.explicitOptions(null);
+ }
+
+ @Test
+ public void createAndGet_Distribution() {
+ Exemplar exemplar = Exemplar.create(15.0, TIMESTAMP, ATTACHMENTS);
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 5.0);
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+ List<Bucket> buckets =
+ Arrays.asList(
+ Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4, exemplar));
+ Distribution distribution = Distribution.create(10, 6.6, 678.54, bucketOptions, buckets);
+ assertThat(distribution.getCount()).isEqualTo(10);
+ assertThat(distribution.getSum()).isWithin(TOLERANCE).of(6.6);
+ assertThat(distribution.getSumOfSquaredDeviations()).isWithin(TOLERANCE).of(678.54);
+
+ final List<Double> actual = new ArrayList<Double>();
+ distribution
+ .getBucketOptions()
+ .match(
+ new Function<ExplicitOptions, Object>() {
+ @Override
+ public Object apply(ExplicitOptions arg) {
+ actual.addAll(arg.getBucketBoundaries());
+ return null;
+ }
+ },
+ Functions.throwAssertionError());
+
+ assertThat(actual).containsExactlyElementsIn(bucketBounds).inOrder();
+
+ assertThat(distribution.getBuckets()).containsExactlyElementsIn(buckets).inOrder();
+ }
+
+ @Test
+ public void createBucket_NegativeCount() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("bucket count should be non-negative.");
+ Bucket.create(-5);
+ }
+
+ @Test
+ public void createExemplar_PreventNullAttachments() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("attachments");
+ Exemplar.create(15, TIMESTAMP, null);
+ }
+
+ @Test
+ public void createExemplar_PreventNullAttachmentKey() {
+ Map<String, String> attachments = Collections.singletonMap(null, "value");
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("key of attachment");
+ Exemplar.create(15, TIMESTAMP, attachments);
+ }
+
+ @Test
+ public void createExemplar_PreventNullAttachmentValue() {
+ Map<String, String> attachments = Collections.singletonMap("key", null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("value of attachment");
+ Exemplar.create(15, TIMESTAMP, attachments);
+ }
+
+ @Test
+ public void createDistribution_NegativeCount() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 5.0);
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4));
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("count should be non-negative.");
+ Distribution.create(-10, 6.6, 678.54, bucketOptions, buckets);
+ }
+
+ @Test
+ public void createDistribution_NegativeSumOfSquaredDeviations() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 5.0);
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(0), Bucket.create(0), Bucket.create(0), Bucket.create(0));
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("sum of squared deviations should be non-negative.");
+ Distribution.create(0, 6.6, -678.54, bucketOptions, buckets);
+ }
+
+ @Test
+ public void createDistribution_ZeroCountAndPositiveMean() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 5.0);
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(0), Bucket.create(0), Bucket.create(0), Bucket.create(0));
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("sum should be 0 if count is 0.");
+ Distribution.create(0, 6.6, 0, bucketOptions, buckets);
+ }
+
+ @Test
+ public void createDistribution_ZeroCountAndSumOfSquaredDeviations() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 5.0);
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(0), Bucket.create(0), Bucket.create(0), Bucket.create(0));
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("sum of squared deviations should be 0 if count is 0.");
+ Distribution.create(0, 0, 678.54, bucketOptions, buckets);
+ }
+
+ @Test
+ public void createDistribution_NullBucketBoundaries() {
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4));
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("bucketBoundaries");
+ Distribution.create(10, 6.6, 678.54, BucketOptions.explicitOptions(null), buckets);
+ }
+
+ @Test
+ public void createDistribution_NullBucketBoundary() {
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4));
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("bucketBoundary");
+ Distribution.create(
+ 10, 6.6, 678.54, BucketOptions.explicitOptions(Arrays.asList(2.5, null)), buckets);
+ }
+
+ @Test
+ public void createDistribution_NullBucketOptions() {
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4));
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("bucketOptions");
+ Distribution.create(10, 6.6, 678.54, null, buckets);
+ }
+
+ @Test
+ public void createDistribution_NullBucketList() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 5.0);
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("buckets");
+ Distribution.create(10, 6.6, 678.54, bucketOptions, null);
+ }
+
+ @Test
+ public void createDistribution_NullBucket() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 5.0);
+ BucketOptions bucketOptions = BucketOptions.explicitOptions(bucketBounds);
+ List<Bucket> buckets =
+ Arrays.asList(Bucket.create(3), Bucket.create(1), null, Bucket.create(4));
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("bucket");
+ Distribution.create(10, 6.6, 678.54, bucketOptions, buckets);
+ }
+
+ @Test
+ public void testEquals() {
+ List<Double> bucketBounds = Arrays.asList(1.0, 2.0, 2.5);
+ new EqualsTester()
+ .addEqualityGroup(
+ Distribution.create(
+ 10,
+ 10,
+ 1,
+ BucketOptions.explicitOptions(bucketBounds),
+ Arrays.asList(
+ Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4))),
+ Distribution.create(
+ 10,
+ 10,
+ 1,
+ BucketOptions.explicitOptions(bucketBounds),
+ Arrays.asList(
+ Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4))))
+ .addEqualityGroup(
+ Distribution.create(
+ 7,
+ 10,
+ 23.456,
+ BucketOptions.explicitOptions(bucketBounds),
+ Arrays.asList(
+ Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4))))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/ExportComponentTest.java b/api/src/test/java/io/opencensus/metrics/export/ExportComponentTest.java
new file mode 100644
index 00000000..15c6e883
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/ExportComponentTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ExportComponent}. */
+@RunWith(JUnit4.class)
+public class ExportComponentTest {
+ @Test
+ public void defaultMetricExporter() {
+ assertThat(ExportComponent.newNoopExportComponent().getMetricProducerManager())
+ .isInstanceOf(MetricProducerManager.class);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/MetricDescriptorTest.java b/api/src/test/java/io/opencensus/metrics/export/MetricDescriptorTest.java
new file mode 100644
index 00000000..502170c6
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/MetricDescriptorTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.metrics.LabelKey;
+import io.opencensus.metrics.export.MetricDescriptor.Type;
+import java.util.Arrays;
+import java.util.List;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link MetricDescriptor}. */
+@RunWith(JUnit4.class)
+public class MetricDescriptorTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ private static final String METRIC_NAME_1 = "metric1";
+ private static final String METRIC_NAME_2 = "metric2";
+ private static final String DESCRIPTION = "Metric description.";
+ private static final String UNIT = "kb/s";
+ private static final LabelKey KEY_1 = LabelKey.create("key1", "some key");
+ private static final LabelKey KEY_2 = LabelKey.create("key2", "some other key");
+
+ @Test
+ public void testGet() {
+ MetricDescriptor metricDescriptor =
+ MetricDescriptor.create(
+ METRIC_NAME_1, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, Arrays.asList(KEY_1, KEY_2));
+ assertThat(metricDescriptor.getName()).isEqualTo(METRIC_NAME_1);
+ assertThat(metricDescriptor.getDescription()).isEqualTo(DESCRIPTION);
+ assertThat(metricDescriptor.getUnit()).isEqualTo(UNIT);
+ assertThat(metricDescriptor.getType()).isEqualTo(Type.GAUGE_DOUBLE);
+ assertThat(metricDescriptor.getLabelKeys()).containsExactly(KEY_1, KEY_2).inOrder();
+ }
+
+ @Test
+ public void preventNullLabelKeyList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("labelKeys"));
+ MetricDescriptor.create(METRIC_NAME_1, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, null);
+ }
+
+ @Test
+ public void preventNullLabelKey() {
+ List<LabelKey> keys = Arrays.asList(KEY_1, null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("labelKey"));
+ MetricDescriptor.create(METRIC_NAME_1, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, keys);
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ MetricDescriptor.create(
+ METRIC_NAME_1, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, Arrays.asList(KEY_1, KEY_2)),
+ MetricDescriptor.create(
+ METRIC_NAME_1, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, Arrays.asList(KEY_1, KEY_2)))
+ .addEqualityGroup(
+ MetricDescriptor.create(
+ METRIC_NAME_2, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, Arrays.asList(KEY_1, KEY_2)))
+ .addEqualityGroup(
+ MetricDescriptor.create(
+ METRIC_NAME_2, DESCRIPTION, UNIT, Type.GAUGE_INT64, Arrays.asList(KEY_1, KEY_2)))
+ .addEqualityGroup(
+ MetricDescriptor.create(
+ METRIC_NAME_1,
+ DESCRIPTION,
+ UNIT,
+ Type.CUMULATIVE_DISTRIBUTION,
+ Arrays.asList(KEY_1, KEY_2)))
+ .addEqualityGroup(
+ MetricDescriptor.create(
+ METRIC_NAME_1,
+ DESCRIPTION,
+ UNIT,
+ Type.CUMULATIVE_DISTRIBUTION,
+ Arrays.asList(KEY_1)))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/MetricProducerManagerTest.java b/api/src/test/java/io/opencensus/metrics/export/MetricProducerManagerTest.java
new file mode 100644
index 00000000..1025427f
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/MetricProducerManagerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link MetricProducerManager}. */
+@RunWith(JUnit4.class)
+public class MetricProducerManagerTest {
+ private final MetricProducerManager metricProducerManager =
+ MetricProducerManager.newNoopMetricProducerManager();
+ @Mock private MetricProducer metricProducer;
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void add_DisallowsNull() {
+ thrown.expect(NullPointerException.class);
+ metricProducerManager.add(null);
+ }
+
+ @Test
+ public void add() {
+ metricProducerManager.add(metricProducer);
+ assertThat(metricProducerManager.getAllMetricProducer()).isEmpty();
+ }
+
+ @Test
+ public void addAndRemove() {
+ metricProducerManager.add(metricProducer);
+ assertThat(metricProducerManager.getAllMetricProducer()).isEmpty();
+ metricProducerManager.remove(metricProducer);
+ assertThat(metricProducerManager.getAllMetricProducer()).isEmpty();
+ }
+
+ @Test
+ public void remove_DisallowsNull() {
+ thrown.expect(NullPointerException.class);
+ metricProducerManager.remove(null);
+ }
+
+ @Test
+ public void remove_FromEmpty() {
+ metricProducerManager.remove(metricProducer);
+ assertThat(metricProducerManager.getAllMetricProducer()).isEmpty();
+ }
+
+ @Test
+ public void getAllMetricProducer_empty() {
+ assertThat(metricProducerManager.getAllMetricProducer()).isEmpty();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/MetricTest.java b/api/src/test/java/io/opencensus/metrics/export/MetricTest.java
new file mode 100644
index 00000000..ed205289
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/MetricTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Timestamp;
+import io.opencensus.metrics.LabelKey;
+import io.opencensus.metrics.LabelValue;
+import io.opencensus.metrics.export.MetricDescriptor.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Metric}. */
+@RunWith(JUnit4.class)
+public class MetricTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ private static final String METRIC_NAME_1 = "metric1";
+ private static final String METRIC_NAME_2 = "metric2";
+ private static final String DESCRIPTION = "Metric description.";
+ private static final String UNIT = "kb/s";
+ private static final LabelKey KEY_1 = LabelKey.create("key1", "some key");
+ private static final LabelKey KEY_2 = LabelKey.create("key1", "some other key");
+ private static final MetricDescriptor METRIC_DESCRIPTOR_1 =
+ MetricDescriptor.create(
+ METRIC_NAME_1, DESCRIPTION, UNIT, Type.GAUGE_DOUBLE, Arrays.asList(KEY_1, KEY_2));
+ private static final MetricDescriptor METRIC_DESCRIPTOR_2 =
+ MetricDescriptor.create(
+ METRIC_NAME_2,
+ DESCRIPTION,
+ UNIT,
+ Type.CUMULATIVE_INT64,
+ Collections.singletonList(KEY_1));
+ private static final LabelValue LABEL_VALUE_1 = LabelValue.create("value1");
+ private static final LabelValue LABEL_VALUE_2 = LabelValue.create("value1");
+ private static final LabelValue LABEL_VALUE_EMPTY = LabelValue.create("");
+ private static final Value VALUE_LONG = Value.longValue(12345678);
+ private static final Value VALUE_DOUBLE_1 = Value.doubleValue(-345.77);
+ private static final Value VALUE_DOUBLE_2 = Value.doubleValue(133.79);
+ private static final Timestamp TIMESTAMP_1 = Timestamp.fromMillis(1000);
+ private static final Timestamp TIMESTAMP_2 = Timestamp.fromMillis(2000);
+ private static final Timestamp TIMESTAMP_3 = Timestamp.fromMillis(3000);
+ private static final Point POINT_1 = Point.create(VALUE_DOUBLE_1, TIMESTAMP_2);
+ private static final Point POINT_2 = Point.create(VALUE_DOUBLE_2, TIMESTAMP_3);
+ private static final Point POINT_3 = Point.create(VALUE_LONG, TIMESTAMP_3);
+ private static final TimeSeries GAUGE_TIME_SERIES_1 =
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Collections.singletonList(POINT_1), null);
+ private static final TimeSeries GAUGE_TIME_SERIES_2 =
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Collections.singletonList(POINT_2), null);
+ private static final TimeSeries CUMULATIVE_TIME_SERIES =
+ TimeSeries.create(
+ Collections.singletonList(LABEL_VALUE_EMPTY),
+ Collections.singletonList(POINT_3),
+ TIMESTAMP_1);
+
+ @Test
+ public void testGet() {
+ Metric metric =
+ Metric.create(METRIC_DESCRIPTOR_1, Arrays.asList(GAUGE_TIME_SERIES_1, GAUGE_TIME_SERIES_2));
+ assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR_1);
+ assertThat(metric.getTimeSeriesList())
+ .containsExactly(GAUGE_TIME_SERIES_1, GAUGE_TIME_SERIES_2)
+ .inOrder();
+ }
+
+ @Test
+ public void typeMismatch_GaugeDouble_Long() {
+ typeMismatch(
+ METRIC_DESCRIPTOR_1,
+ Collections.singletonList(CUMULATIVE_TIME_SERIES),
+ String.format("Type mismatch: %s, %s.", Type.GAUGE_DOUBLE, "ValueLong"));
+ }
+
+ @Test
+ public void typeMismatch_CumulativeInt64_Double() {
+ typeMismatch(
+ METRIC_DESCRIPTOR_2,
+ Collections.singletonList(GAUGE_TIME_SERIES_1),
+ String.format("Type mismatch: %s, %s.", Type.CUMULATIVE_INT64, "ValueDouble"));
+ }
+
+ private void typeMismatch(
+ MetricDescriptor metricDescriptor, List<TimeSeries> timeSeriesList, String errorMessage) {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage(errorMessage);
+ Metric.create(metricDescriptor, timeSeriesList);
+ }
+
+ @Test
+ public void create_WithNullMetricDescriptor() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("metricDescriptor");
+ Metric.create(null, Collections.<TimeSeries>emptyList());
+ }
+
+ @Test
+ public void create_WithNullTimeSeriesList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("timeSeriesList");
+ Metric.create(METRIC_DESCRIPTOR_1, null);
+ }
+
+ @Test
+ public void create_WithNullTimeSeries() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("timeSeries");
+ Metric.create(METRIC_DESCRIPTOR_1, Arrays.asList(GAUGE_TIME_SERIES_1, null));
+ }
+
+ @Test
+ public void immutableTimeSeriesList() {
+ List<TimeSeries> timeSeriesList = new ArrayList<TimeSeries>();
+ timeSeriesList.add(GAUGE_TIME_SERIES_1);
+ Metric metric = Metric.create(METRIC_DESCRIPTOR_1, timeSeriesList);
+ timeSeriesList.add(GAUGE_TIME_SERIES_2);
+ assertThat(metric.getTimeSeriesList()).containsExactly(GAUGE_TIME_SERIES_1);
+ }
+
+ @Test
+ public void createWithOneTimeSeries_WithNullTimeSeries() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("timeSeries");
+ Metric.createWithOneTimeSeries(METRIC_DESCRIPTOR_1, null);
+ }
+
+ @Test
+ public void createWithOneTimeSeries_WithNullMetricDescriptor() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("metricDescriptor");
+ Metric.createWithOneTimeSeries(null, GAUGE_TIME_SERIES_1);
+ }
+
+ @Test
+ public void testGet_WithOneTimeSeries() {
+ Metric metric = Metric.createWithOneTimeSeries(METRIC_DESCRIPTOR_1, GAUGE_TIME_SERIES_1);
+ assertThat(metric.getMetricDescriptor()).isEqualTo(METRIC_DESCRIPTOR_1);
+ assertThat(metric.getTimeSeriesList()).containsExactly(GAUGE_TIME_SERIES_1);
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ Metric.create(
+ METRIC_DESCRIPTOR_1, Arrays.asList(GAUGE_TIME_SERIES_1, GAUGE_TIME_SERIES_2)),
+ Metric.create(
+ METRIC_DESCRIPTOR_1, Arrays.asList(GAUGE_TIME_SERIES_1, GAUGE_TIME_SERIES_2)))
+ .addEqualityGroup(Metric.create(METRIC_DESCRIPTOR_1, Collections.<TimeSeries>emptyList()))
+ .addEqualityGroup(
+ Metric.createWithOneTimeSeries(METRIC_DESCRIPTOR_2, CUMULATIVE_TIME_SERIES))
+ .addEqualityGroup(Metric.create(METRIC_DESCRIPTOR_2, Collections.<TimeSeries>emptyList()))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/PointTest.java b/api/src/test/java/io/opencensus/metrics/export/PointTest.java
new file mode 100644
index 00000000..cdfc7792
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/PointTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Timestamp;
+import io.opencensus.metrics.export.Distribution.Bucket;
+import io.opencensus.metrics.export.Distribution.BucketOptions;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Point}. */
+@RunWith(JUnit4.class)
+public class PointTest {
+
+ private static final Value DOUBLE_VALUE = Value.doubleValue(55.5);
+ private static final Value LONG_VALUE = Value.longValue(9876543210L);
+ private static final Value DISTRIBUTION_VALUE =
+ Value.distributionValue(
+ Distribution.create(
+ 10,
+ 6.6,
+ 678.54,
+ BucketOptions.explicitOptions(Arrays.asList(1.0, 2.0, 5.0)),
+ Arrays.asList(
+ Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4))));
+ private static final Timestamp TIMESTAMP_1 = Timestamp.create(1, 2);
+ private static final Timestamp TIMESTAMP_2 = Timestamp.create(3, 4);
+ private static final Timestamp TIMESTAMP_3 = Timestamp.create(5, 6);
+
+ @Test
+ public void testGet() {
+ Point point = Point.create(DOUBLE_VALUE, TIMESTAMP_1);
+ assertThat(point.getValue()).isEqualTo(DOUBLE_VALUE);
+ assertThat(point.getTimestamp()).isEqualTo(TIMESTAMP_1);
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ Point.create(DOUBLE_VALUE, TIMESTAMP_1), Point.create(DOUBLE_VALUE, TIMESTAMP_1))
+ .addEqualityGroup(Point.create(LONG_VALUE, TIMESTAMP_1))
+ .addEqualityGroup(Point.create(LONG_VALUE, TIMESTAMP_2))
+ .addEqualityGroup(
+ Point.create(DISTRIBUTION_VALUE, TIMESTAMP_2),
+ Point.create(DISTRIBUTION_VALUE, TIMESTAMP_2))
+ .addEqualityGroup(Point.create(DISTRIBUTION_VALUE, TIMESTAMP_3))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/SummaryTest.java b/api/src/test/java/io/opencensus/metrics/export/SummaryTest.java
new file mode 100644
index 00000000..c10df043
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/SummaryTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.metrics.export.Summary.Snapshot;
+import io.opencensus.metrics.export.Summary.Snapshot.ValueAtPercentile;
+import java.util.Collections;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Summary}. */
+@RunWith(JUnit4.class)
+public class SummaryTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+ private static final double TOLERANCE = 1e-6;
+
+ @Test
+ public void createAndGet_ValueAtPercentile() {
+ ValueAtPercentile valueAtPercentile = ValueAtPercentile.create(99.5, 10.2);
+ assertThat(valueAtPercentile.getPercentile()).isWithin(TOLERANCE).of(99.5);
+ assertThat(valueAtPercentile.getValue()).isWithin(TOLERANCE).of(10.2);
+ }
+
+ @Test
+ public void createValueAtPercentile_InvalidValueAtPercentileInterval() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("percentile must be in the interval (0.0, 100.0]");
+ ValueAtPercentile.create(100.1, 10.2);
+ }
+
+ @Test
+ public void createValueAtPercentile_NegativeValue() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("value must be non-negative");
+ ValueAtPercentile.create(99.5, -10.2);
+ }
+
+ @Test
+ public void createAndGet_Snapshot() {
+ Snapshot snapshot =
+ Snapshot.create(
+ 10L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)));
+ assertThat(snapshot.getCount()).isEqualTo(10);
+ assertThat(snapshot.getSum()).isWithin(TOLERANCE).of(87.07);
+ assertThat(snapshot.getValueAtPercentiles())
+ .containsExactly(ValueAtPercentile.create(99.5, 10.2));
+ }
+
+ @Test
+ public void createAndGet_Snapshot_WithNullCountAndSum() {
+ Snapshot snapshot =
+ Snapshot.create(
+ null, null, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)));
+ assertThat(snapshot.getCount()).isNull();
+ assertThat(snapshot.getSum()).isNull();
+ assertThat(snapshot.getValueAtPercentiles())
+ .containsExactly(ValueAtPercentile.create(99.5, 10.2));
+ }
+
+ @Test
+ public void createSnapshot_NegativeCount() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("count must be non-negative");
+ Snapshot.create(-10L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)));
+ }
+
+ @Test
+ public void createSnapshot_NegativeSum() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("sum must be non-negative");
+ Snapshot.create(10L, -87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)));
+ }
+
+ @Test
+ public void createSnapshot_ZeroCountAndNonZeroSum() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("sum must be 0 if count is 0");
+ Snapshot.create(0L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)));
+ }
+
+ @Test
+ public void createSnapshot_NullValueAtPercentilesList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("valueAtPercentiles");
+ Snapshot.create(10L, 87.07, null);
+ }
+
+ @Test
+ public void createSnapshot_OneNullValueAtPercentile() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("value in valueAtPercentiles");
+ Snapshot.create(10L, 87.07, Collections.<ValueAtPercentile>singletonList(null));
+ }
+
+ @Test
+ public void createAndGet_Summary() {
+ Snapshot snapshot =
+ Snapshot.create(
+ 10L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)));
+ Summary summary = Summary.create(10L, 6.6, snapshot);
+ assertThat(summary.getCount()).isEqualTo(10);
+ assertThat(summary.getSum()).isWithin(TOLERANCE).of(6.6);
+ assertThat(summary.getSnapshot()).isEqualTo(snapshot);
+ }
+
+ @Test
+ public void createSummary_NegativeCount() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("count must be non-negative");
+ Summary.create(
+ -10L, 6.6, Snapshot.create(null, null, Collections.<ValueAtPercentile>emptyList()));
+ }
+
+ @Test
+ public void createSummary_NegativeSum() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("sum must be non-negative");
+ Summary.create(
+ 10L, -6.6, Snapshot.create(null, null, Collections.<ValueAtPercentile>emptyList()));
+ }
+
+ @Test
+ public void createSummary_ZeroCountAndNonZeroSum() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("sum must be 0 if count is 0");
+ Summary.create(
+ 0L, 6.6, Snapshot.create(null, null, Collections.<ValueAtPercentile>emptyList()));
+ }
+
+ @Test
+ public void createSummary_NullSnapshot() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("snapshot");
+ Summary.create(10L, 6.6, null);
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ Summary.create(
+ 10L,
+ 10.0,
+ Snapshot.create(
+ 10L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)))),
+ Summary.create(
+ 10L,
+ 10.0,
+ Snapshot.create(
+ 10L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)))))
+ .addEqualityGroup(
+ Summary.create(
+ 7L,
+ 10.0,
+ Snapshot.create(
+ 10L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)))))
+ .addEqualityGroup(
+ Summary.create(
+ 10L,
+ 7.0,
+ Snapshot.create(
+ 10L, 87.07, Collections.singletonList(ValueAtPercentile.create(99.5, 10.2)))))
+ .addEqualityGroup(
+ Summary.create(
+ 10L, 10.0, Snapshot.create(null, null, Collections.<ValueAtPercentile>emptyList())))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/TimeSeriesTest.java b/api/src/test/java/io/opencensus/metrics/export/TimeSeriesTest.java
new file mode 100644
index 00000000..92a2c8cf
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/TimeSeriesTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Timestamp;
+import io.opencensus.metrics.LabelValue;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TimeSeries}. */
+@RunWith(JUnit4.class)
+public class TimeSeriesTest {
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ private static final LabelValue LABEL_VALUE_1 = LabelValue.create("value1");
+ private static final LabelValue LABEL_VALUE_2 = LabelValue.create("value2");
+ private static final Value VALUE_LONG = Value.longValue(12345678);
+ private static final Value VALUE_DOUBLE = Value.doubleValue(-345.77);
+ private static final Timestamp TIMESTAMP_1 = Timestamp.fromMillis(1000);
+ private static final Timestamp TIMESTAMP_2 = Timestamp.fromMillis(2000);
+ private static final Timestamp TIMESTAMP_3 = Timestamp.fromMillis(3000);
+ private static final Point POINT_1 = Point.create(VALUE_DOUBLE, TIMESTAMP_2);
+ private static final Point POINT_2 = Point.create(VALUE_LONG, TIMESTAMP_3);
+
+ @Test
+ public void testGet_TimeSeries() {
+ TimeSeries cumulativeTimeSeries =
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Arrays.asList(POINT_1), TIMESTAMP_1);
+ assertThat(cumulativeTimeSeries.getStartTimestamp()).isEqualTo(TIMESTAMP_1);
+ assertThat(cumulativeTimeSeries.getLabelValues())
+ .containsExactly(LABEL_VALUE_1, LABEL_VALUE_2)
+ .inOrder();
+ assertThat(cumulativeTimeSeries.getPoints()).containsExactly(POINT_1).inOrder();
+ }
+
+ @Test
+ public void create_WithNullLabelValueList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("labelValues"));
+ TimeSeries.create(null, Collections.<Point>emptyList(), TIMESTAMP_1);
+ }
+
+ @Test
+ public void create_WithNullLabelValue() {
+ List<LabelValue> labelValues = Arrays.asList(LABEL_VALUE_1, null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("labelValue"));
+ TimeSeries.create(labelValues, Collections.<Point>emptyList(), TIMESTAMP_1);
+ }
+
+ @Test
+ public void create_WithNullPointList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("points"));
+ TimeSeries.create(Collections.<LabelValue>emptyList(), null, TIMESTAMP_1);
+ }
+
+ @Test
+ public void create_WithNullPoint() {
+ List<Point> points = Arrays.asList(POINT_1, null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("point"));
+ TimeSeries.create(Collections.<LabelValue>emptyList(), points, TIMESTAMP_1);
+ }
+
+ @Test
+ public void testGet_WithOnePointTimeSeries() {
+ TimeSeries cumulativeTimeSeries =
+ TimeSeries.createWithOnePoint(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), POINT_1, TIMESTAMP_1);
+ assertThat(cumulativeTimeSeries.getStartTimestamp()).isEqualTo(TIMESTAMP_1);
+ assertThat(cumulativeTimeSeries.getLabelValues())
+ .containsExactly(LABEL_VALUE_1, LABEL_VALUE_2)
+ .inOrder();
+ assertThat(cumulativeTimeSeries.getPoints()).containsExactly(POINT_1).inOrder();
+ }
+
+ @Test
+ public void createWithOnePoint_WithNullLabelValueList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("labelValues"));
+ TimeSeries.createWithOnePoint(null, POINT_1, TIMESTAMP_1);
+ }
+
+ @Test
+ public void createWithOnePoint_WithNullLabelValue() {
+ List<LabelValue> labelValues = Arrays.asList(LABEL_VALUE_1, null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("labelValue"));
+ TimeSeries.createWithOnePoint(labelValues, POINT_1, TIMESTAMP_1);
+ }
+
+ @Test
+ public void createWithOnePoint_WithNullPointList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage(CoreMatchers.equalTo("point"));
+ TimeSeries.createWithOnePoint(Collections.<LabelValue>emptyList(), null, TIMESTAMP_1);
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Arrays.asList(POINT_1), TIMESTAMP_1),
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Arrays.asList(POINT_1), TIMESTAMP_1))
+ .addEqualityGroup(
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Arrays.asList(POINT_1), null),
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Arrays.asList(POINT_1), null))
+ .addEqualityGroup(
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1, LABEL_VALUE_2), Arrays.asList(POINT_1), TIMESTAMP_2))
+ .addEqualityGroup(
+ TimeSeries.create(Arrays.asList(LABEL_VALUE_1), Arrays.asList(POINT_1), TIMESTAMP_2))
+ .addEqualityGroup(
+ TimeSeries.create(Arrays.asList(LABEL_VALUE_1), Arrays.asList(POINT_2), TIMESTAMP_2))
+ .addEqualityGroup(
+ TimeSeries.create(
+ Arrays.asList(LABEL_VALUE_1), Arrays.asList(POINT_1, POINT_2), TIMESTAMP_2))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/metrics/export/ValueTest.java b/api/src/test/java/io/opencensus/metrics/export/ValueTest.java
new file mode 100644
index 00000000..bf947692
--- /dev/null
+++ b/api/src/test/java/io/opencensus/metrics/export/ValueTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.metrics.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.metrics.export.Distribution.Bucket;
+import io.opencensus.metrics.export.Distribution.BucketOptions;
+import io.opencensus.metrics.export.Distribution.BucketOptions.ExplicitOptions;
+import io.opencensus.metrics.export.Summary.Snapshot;
+import io.opencensus.metrics.export.Summary.Snapshot.ValueAtPercentile;
+import io.opencensus.metrics.export.Value.ValueDistribution;
+import io.opencensus.metrics.export.Value.ValueDouble;
+import io.opencensus.metrics.export.Value.ValueLong;
+import io.opencensus.metrics.export.Value.ValueSummary;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Value}. */
+@RunWith(JUnit4.class)
+public class ValueTest {
+ private static final double TOLERANCE = 1e-6;
+
+ private static final Distribution DISTRIBUTION =
+ Distribution.create(
+ 10,
+ 10,
+ 1,
+ BucketOptions.explicitOptions(Arrays.asList(1.0, 2.0, 5.0)),
+ Arrays.asList(Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4)));
+ private static final Summary SUMMARY =
+ Summary.create(
+ 10L,
+ 10.0,
+ Snapshot.create(
+ 10L, 87.07, Collections.singletonList(ValueAtPercentile.create(0.98, 10.2))));
+
+ @Test
+ public void createAndGet_ValueDouble() {
+ Value value = Value.doubleValue(-34.56);
+ assertThat(value).isInstanceOf(ValueDouble.class);
+ assertThat(((ValueDouble) value).getValue()).isWithin(TOLERANCE).of(-34.56);
+ }
+
+ @Test
+ public void createAndGet_ValueLong() {
+ Value value = Value.longValue(123456789);
+ assertThat(value).isInstanceOf(ValueLong.class);
+ assertThat(((ValueLong) value).getValue()).isEqualTo(123456789);
+ }
+
+ @Test
+ public void createAndGet_ValueDistribution() {
+ Value value = Value.distributionValue(DISTRIBUTION);
+ assertThat(value).isInstanceOf(ValueDistribution.class);
+ assertThat(((ValueDistribution) value).getValue()).isEqualTo(DISTRIBUTION);
+ }
+
+ @Test
+ public void createAndGet_ValueSummary() {
+ Value value = Value.summaryValue(SUMMARY);
+ assertThat(value).isInstanceOf(ValueSummary.class);
+ assertThat(((ValueSummary) value).getValue()).isEqualTo(SUMMARY);
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(Value.doubleValue(1.0), Value.doubleValue(1.0))
+ .addEqualityGroup(Value.doubleValue(2.0))
+ .addEqualityGroup(Value.longValue(1L))
+ .addEqualityGroup(Value.longValue(2L))
+ .addEqualityGroup(
+ Value.distributionValue(
+ Distribution.create(
+ 7,
+ 10,
+ 23.456,
+ BucketOptions.explicitOptions(Arrays.asList(1.0, 2.0, 5.0)),
+ Arrays.asList(
+ Bucket.create(3), Bucket.create(1), Bucket.create(2), Bucket.create(4)))))
+ .testEquals();
+ }
+
+ @Test
+ public void testMatch() {
+ List<Value> values =
+ Arrays.asList(
+ ValueDouble.create(1.0),
+ ValueLong.create(-1),
+ ValueDistribution.create(DISTRIBUTION),
+ ValueSummary.create(SUMMARY));
+ List<Number> expected =
+ Arrays.<Number>asList(1.0, -1L, 10.0, 10L, 1.0, 1.0, 2.0, 5.0, 3L, 1L, 2L, 4L);
+ final List<Number> actual = new ArrayList<Number>();
+ for (Value value : values) {
+ value.match(
+ new Function<Double, Object>() {
+ @Override
+ public Object apply(Double arg) {
+ actual.add(arg);
+ return null;
+ }
+ },
+ new Function<Long, Object>() {
+ @Override
+ public Object apply(Long arg) {
+ actual.add(arg);
+ return null;
+ }
+ },
+ new Function<Distribution, Object>() {
+ @Override
+ public Object apply(Distribution arg) {
+ actual.add(arg.getSum());
+ actual.add(arg.getCount());
+ actual.add(arg.getSumOfSquaredDeviations());
+
+ arg.getBucketOptions()
+ .match(
+ new Function<ExplicitOptions, Object>() {
+ @Override
+ public Object apply(ExplicitOptions arg) {
+ actual.addAll(arg.getBucketBoundaries());
+ return null;
+ }
+ },
+ Functions.throwAssertionError());
+
+ for (Bucket bucket : arg.getBuckets()) {
+ actual.add(bucket.getCount());
+ }
+ return null;
+ }
+ },
+ new Function<Summary, Object>() {
+ @Override
+ public Object apply(Summary arg) {
+ return null;
+ }
+ },
+ Functions.throwAssertionError());
+ }
+ assertThat(actual).containsExactlyElementsIn(expected).inOrder();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/AggregationDataTest.java b/api/src/test/java/io/opencensus/stats/AggregationDataTest.java
new file mode 100644
index 00000000..a6d6d1de
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/AggregationDataTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.stats.AggregationData.CountData;
+import io.opencensus.stats.AggregationData.DistributionData;
+import io.opencensus.stats.AggregationData.DistributionData.Exemplar;
+import io.opencensus.stats.AggregationData.LastValueDataDouble;
+import io.opencensus.stats.AggregationData.LastValueDataLong;
+import io.opencensus.stats.AggregationData.MeanData;
+import io.opencensus.stats.AggregationData.SumDataDouble;
+import io.opencensus.stats.AggregationData.SumDataLong;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link io.opencensus.stats.AggregationData}. */
+@RunWith(JUnit4.class)
+public class AggregationDataTest {
+
+ private static final double TOLERANCE = 1e-6;
+ private static final Timestamp TIMESTAMP_1 = Timestamp.create(1, 0);
+ private static final Timestamp TIMESTAMP_2 = Timestamp.create(2, 0);
+ private static final Map<String, String> ATTACHMENTS = Collections.singletonMap("key", "value");
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testCreateDistributionData() {
+ DistributionData distributionData =
+ DistributionData.create(7.7, 10, 1.1, 9.9, 32.2, Arrays.asList(4L, 1L, 5L));
+ assertThat(distributionData.getMean()).isWithin(TOLERANCE).of(7.7);
+ assertThat(distributionData.getCount()).isEqualTo(10);
+ assertThat(distributionData.getMin()).isWithin(TOLERANCE).of(1.1);
+ assertThat(distributionData.getMax()).isWithin(TOLERANCE).of(9.9);
+ assertThat(distributionData.getSumOfSquaredDeviations()).isWithin(TOLERANCE).of(32.2);
+ assertThat(distributionData.getBucketCounts()).containsExactly(4L, 1L, 5L).inOrder();
+ }
+
+ @Test
+ public void testCreateDistributionDataWithExemplar() {
+ Exemplar exemplar1 = Exemplar.create(4, TIMESTAMP_2, ATTACHMENTS);
+ Exemplar exemplar2 = Exemplar.create(1, TIMESTAMP_1, ATTACHMENTS);
+ DistributionData distributionData =
+ DistributionData.create(
+ 7.7, 10, 1.1, 9.9, 32.2, Arrays.asList(4L, 1L), Arrays.asList(exemplar1, exemplar2));
+ assertThat(distributionData.getExemplars()).containsExactly(exemplar1, exemplar2).inOrder();
+ }
+
+ @Test
+ public void testExemplar() {
+ Exemplar exemplar = Exemplar.create(15.0, TIMESTAMP_1, ATTACHMENTS);
+ assertThat(exemplar.getValue()).isEqualTo(15.0);
+ assertThat(exemplar.getTimestamp()).isEqualTo(TIMESTAMP_1);
+ assertThat(exemplar.getAttachments()).isEqualTo(ATTACHMENTS);
+ }
+
+ @Test
+ public void testExemplar_PreventNullAttachments() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("attachments");
+ Exemplar.create(15, TIMESTAMP_1, null);
+ }
+
+ @Test
+ public void testExemplar_PreventNullAttachmentKey() {
+ Map<String, String> attachments = Collections.singletonMap(null, "value");
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("key of attachment");
+ Exemplar.create(15, TIMESTAMP_1, attachments);
+ }
+
+ @Test
+ public void testExemplar_PreventNullAttachmentValue() {
+ Map<String, String> attachments = Collections.singletonMap("key", null);
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("value of attachment");
+ Exemplar.create(15, TIMESTAMP_1, attachments);
+ }
+
+ @Test
+ public void preventNullBucketCountList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("bucketCounts");
+ DistributionData.create(1, 1, 1, 1, 0, null);
+ }
+
+ @Test
+ public void preventNullBucket() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("bucket");
+ DistributionData.create(1, 1, 1, 1, 0, Arrays.asList(0L, 1L, null));
+ }
+
+ @Test
+ public void preventNullExemplarList() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("exemplar list should not be null.");
+ DistributionData.create(1, 1, 1, 1, 0, Arrays.asList(0L, 1L, 1L), null);
+ }
+
+ @Test
+ public void preventNullExemplar() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("exemplar");
+ DistributionData.create(
+ 1, 1, 1, 1, 0, Arrays.asList(0L, 1L, 1L), Collections.<Exemplar>singletonList(null));
+ }
+
+ @Test
+ public void preventMinIsGreaterThanMax() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("max should be greater or equal to min.");
+ DistributionData.create(1, 1, 10, 1, 0, Arrays.asList(0L, 1L, 0L));
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(SumDataDouble.create(10.0), SumDataDouble.create(10.0))
+ .addEqualityGroup(SumDataDouble.create(20.0), SumDataDouble.create(20.0))
+ .addEqualityGroup(SumDataLong.create(20), SumDataLong.create(20))
+ .addEqualityGroup(CountData.create(40), CountData.create(40))
+ .addEqualityGroup(CountData.create(80), CountData.create(80))
+ .addEqualityGroup(
+ DistributionData.create(10, 10, 1, 1, 0, Arrays.asList(0L, 10L, 0L)),
+ DistributionData.create(10, 10, 1, 1, 0, Arrays.asList(0L, 10L, 0L)))
+ .addEqualityGroup(DistributionData.create(10, 10, 1, 1, 0, Arrays.asList(0L, 10L, 100L)))
+ .addEqualityGroup(DistributionData.create(110, 10, 1, 1, 0, Arrays.asList(0L, 10L, 0L)))
+ .addEqualityGroup(DistributionData.create(10, 110, 1, 1, 0, Arrays.asList(0L, 10L, 0L)))
+ .addEqualityGroup(DistributionData.create(10, 10, -1, 1, 0, Arrays.asList(0L, 10L, 0L)))
+ .addEqualityGroup(DistributionData.create(10, 10, 1, 5, 0, Arrays.asList(0L, 10L, 0L)))
+ .addEqualityGroup(DistributionData.create(10, 10, 1, 1, 55.5, Arrays.asList(0L, 10L, 0L)))
+ .addEqualityGroup(MeanData.create(5.0, 1), MeanData.create(5.0, 1))
+ .addEqualityGroup(MeanData.create(-5.0, 1), MeanData.create(-5.0, 1))
+ .addEqualityGroup(LastValueDataDouble.create(20.0), LastValueDataDouble.create(20.0))
+ .addEqualityGroup(LastValueDataLong.create(20), LastValueDataLong.create(20))
+ .testEquals();
+ }
+
+ @Test
+ public void testMatchAndGet() {
+ List<AggregationData> aggregations =
+ Arrays.asList(
+ SumDataDouble.create(10.0),
+ SumDataLong.create(100000000),
+ CountData.create(40),
+ DistributionData.create(1, 1, 1, 1, 0, Arrays.asList(0L, 10L, 0L)),
+ LastValueDataDouble.create(20.0),
+ LastValueDataLong.create(200000000L));
+
+ final List<Object> actual = new ArrayList<Object>();
+ for (AggregationData aggregation : aggregations) {
+ aggregation.match(
+ new Function<SumDataDouble, Void>() {
+ @Override
+ public Void apply(SumDataDouble arg) {
+ actual.add(arg.getSum());
+ return null;
+ }
+ },
+ new Function<SumDataLong, Void>() {
+ @Override
+ public Void apply(SumDataLong arg) {
+ actual.add(arg.getSum());
+ return null;
+ }
+ },
+ new Function<CountData, Void>() {
+ @Override
+ public Void apply(CountData arg) {
+ actual.add(arg.getCount());
+ return null;
+ }
+ },
+ new Function<DistributionData, Void>() {
+ @Override
+ public Void apply(DistributionData arg) {
+ actual.add(arg.getBucketCounts());
+ return null;
+ }
+ },
+ new Function<LastValueDataDouble, Void>() {
+ @Override
+ public Void apply(LastValueDataDouble arg) {
+ actual.add(arg.getLastValue());
+ return null;
+ }
+ },
+ new Function<LastValueDataLong, Void>() {
+ @Override
+ public Void apply(LastValueDataLong arg) {
+ actual.add(arg.getLastValue());
+ return null;
+ }
+ },
+ Functions.<Void>throwIllegalArgumentException());
+ }
+
+ assertThat(actual)
+ .containsExactly(10.0, 100000000L, 40L, Arrays.asList(0L, 10L, 0L), 20.0, 200000000L)
+ .inOrder();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/AggregationTest.java b/api/src/test/java/io/opencensus/stats/AggregationTest.java
new file mode 100644
index 00000000..cf337030
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/AggregationTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Functions;
+import io.opencensus.stats.Aggregation.Count;
+import io.opencensus.stats.Aggregation.Distribution;
+import io.opencensus.stats.Aggregation.LastValue;
+import io.opencensus.stats.Aggregation.Mean;
+import io.opencensus.stats.Aggregation.Sum;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link io.opencensus.stats.Aggregation}. */
+@RunWith(JUnit4.class)
+public class AggregationTest {
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testCreateDistribution() {
+ BucketBoundaries bucketBoundaries = BucketBoundaries.create(Arrays.asList(0.1, 2.2, 33.3));
+ Distribution distribution = Distribution.create(bucketBoundaries);
+ assertThat(distribution.getBucketBoundaries()).isEqualTo(bucketBoundaries);
+ }
+
+ @Test
+ public void testNullBucketBoundaries() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("bucketBoundaries");
+ Distribution.create(null);
+ }
+
+ @Test
+ public void testEquals() {
+ new EqualsTester()
+ .addEqualityGroup(Sum.create(), Sum.create())
+ .addEqualityGroup(Count.create(), Count.create())
+ .addEqualityGroup(
+ Distribution.create(BucketBoundaries.create(Arrays.asList(-10.0, 1.0, 5.0))),
+ Distribution.create(BucketBoundaries.create(Arrays.asList(-10.0, 1.0, 5.0))))
+ .addEqualityGroup(
+ Distribution.create(BucketBoundaries.create(Arrays.asList(0.0, 1.0, 5.0))),
+ Distribution.create(BucketBoundaries.create(Arrays.asList(0.0, 1.0, 5.0))))
+ .addEqualityGroup(Mean.create(), Mean.create())
+ .addEqualityGroup(LastValue.create(), LastValue.create())
+ .testEquals();
+ }
+
+ @Test
+ public void testMatch() {
+ List<Aggregation> aggregations =
+ Arrays.asList(
+ Sum.create(),
+ Count.create(),
+ Mean.create(),
+ Distribution.create(BucketBoundaries.create(Arrays.asList(-10.0, 1.0, 5.0))),
+ LastValue.create());
+
+ List<String> actual = new ArrayList<String>();
+ for (Aggregation aggregation : aggregations) {
+ actual.add(
+ aggregation.match(
+ Functions.returnConstant("SUM"),
+ Functions.returnConstant("COUNT"),
+ Functions.returnConstant("DISTRIBUTION"),
+ Functions.returnConstant("LASTVALUE"),
+ Functions.returnConstant("UNKNOWN")));
+ }
+
+ assertThat(actual)
+ .isEqualTo(Arrays.asList("SUM", "COUNT", "UNKNOWN", "DISTRIBUTION", "LASTVALUE"));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/BucketBoundariesTest.java b/api/src/test/java/io/opencensus/stats/BucketBoundariesTest.java
new file mode 100644
index 00000000..36f2edb4
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/BucketBoundariesTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link io.opencensus.stats.BucketBoundaries}. */
+@RunWith(JUnit4.class)
+public class BucketBoundariesTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testConstructBoundaries() {
+ List<Double> buckets = Arrays.asList(0.0, 1.0, 2.0);
+ BucketBoundaries bucketBoundaries = BucketBoundaries.create(buckets);
+ assertThat(bucketBoundaries.getBoundaries()).isEqualTo(buckets);
+ }
+
+ @Test
+ public void testBoundariesDoesNotChangeWithOriginalList() {
+ List<Double> original = new ArrayList<Double>();
+ original.add(0.0);
+ original.add(1.0);
+ original.add(2.0);
+ BucketBoundaries bucketBoundaries = BucketBoundaries.create(original);
+ original.set(2, 3.0);
+ original.add(4.0);
+ List<Double> expected = Arrays.asList(0.0, 1.0, 2.0);
+ assertThat(bucketBoundaries.getBoundaries()).isNotEqualTo(original);
+ assertThat(bucketBoundaries.getBoundaries()).isEqualTo(expected);
+ }
+
+ @Test
+ public void testNullBoundaries() throws Exception {
+ thrown.expect(NullPointerException.class);
+ BucketBoundaries.create(null);
+ }
+
+ @Test
+ public void testUnsortedBoundaries() throws Exception {
+ List<Double> buckets = Arrays.asList(0.0, 1.0, 1.0);
+ thrown.expect(IllegalArgumentException.class);
+ BucketBoundaries.create(buckets);
+ }
+
+ @Test
+ public void testNoBoundaries() {
+ List<Double> buckets = Arrays.asList();
+ BucketBoundaries bucketBoundaries = BucketBoundaries.create(buckets);
+ assertThat(bucketBoundaries.getBoundaries()).isEqualTo(buckets);
+ }
+
+ @Test
+ public void testBucketBoundariesEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ BucketBoundaries.create(Arrays.asList(-1.0, 2.0)),
+ BucketBoundaries.create(Arrays.asList(-1.0, 2.0)))
+ .addEqualityGroup(BucketBoundaries.create(Arrays.asList(-1.0)))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/MeasureTest.java b/api/src/test/java/io/opencensus/stats/MeasureTest.java
new file mode 100644
index 00000000..a9302425
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/MeasureTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Measure.MeasureLong;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Measure}. */
+@RunWith(JUnit4.class)
+public final class MeasureTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testConstants() {
+ assertThat(Measure.NAME_MAX_LENGTH).isEqualTo(255);
+ }
+
+ @Test
+ public void preventTooLongMeasureName() {
+ char[] chars = new char[Measure.NAME_MAX_LENGTH + 1];
+ Arrays.fill(chars, 'a');
+ String longName = String.valueOf(chars);
+ thrown.expect(IllegalArgumentException.class);
+ Measure.MeasureDouble.create(longName, "description", "1");
+ }
+
+ @Test
+ public void preventNonPrintableMeasureName() {
+ thrown.expect(IllegalArgumentException.class);
+ Measure.MeasureDouble.create("\2", "description", "1");
+ }
+
+ @Test
+ public void testMeasureDoubleComponents() {
+ Measure measurement = Measure.MeasureDouble.create("Foo", "The description of Foo", "Mbit/s");
+ assertThat(measurement.getName()).isEqualTo("Foo");
+ assertThat(measurement.getDescription()).isEqualTo("The description of Foo");
+ assertThat(measurement.getUnit()).isEqualTo("Mbit/s");
+ }
+
+ @Test
+ public void testMeasureLongComponents() {
+ Measure measurement = Measure.MeasureLong.create("Bar", "The description of Bar", "1");
+ assertThat(measurement.getName()).isEqualTo("Bar");
+ assertThat(measurement.getDescription()).isEqualTo("The description of Bar");
+ assertThat(measurement.getUnit()).isEqualTo("1");
+ }
+
+ @Test
+ public void testMeasureDoubleEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ Measure.MeasureDouble.create("name", "description", "bit/s"),
+ Measure.MeasureDouble.create("name", "description", "bit/s"))
+ .addEqualityGroup(Measure.MeasureDouble.create("name", "description 2", "bit/s"))
+ .testEquals();
+ }
+
+ @Test
+ public void testMeasureLongEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ Measure.MeasureLong.create("name", "description", "bit/s"),
+ Measure.MeasureLong.create("name", "description", "bit/s"))
+ .addEqualityGroup(Measure.MeasureLong.create("name", "description 2", "bit/s"))
+ .testEquals();
+ }
+
+ @Test
+ public void testMatch() {
+ List<Measure> measures =
+ Arrays.asList(
+ MeasureDouble.create("measure1", "description", "1"),
+ MeasureLong.create("measure2", "description", "1"));
+ List<String> outputs = Lists.newArrayList();
+ for (Measure measure : measures) {
+ outputs.add(
+ measure.match(
+ new Function<MeasureDouble, String>() {
+ @Override
+ public String apply(MeasureDouble arg) {
+ return "double";
+ }
+ },
+ new Function<MeasureLong, String>() {
+ @Override
+ public String apply(MeasureLong arg) {
+ return "long";
+ }
+ },
+ Functions.<String>throwAssertionError()));
+ }
+ assertThat(outputs).containsExactly("double", "long").inOrder();
+ }
+
+ @Test
+ public void testMeasureDoubleIsNotEqualToMeasureLong() {
+ assertThat(Measure.MeasureDouble.create("name", "description", "bit/s"))
+ .isNotEqualTo(Measure.MeasureLong.create("name", "description", "bit/s"));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/NoopStatsTest.java b/api/src/test/java/io/opencensus/stats/NoopStatsTest.java
new file mode 100644
index 00000000..4bae14a6
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/NoopStatsTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.tags.Tag;
+import io.opencensus.tags.TagContext;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import java.util.Collections;
+import java.util.Iterator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link NoopStats}. Tests for {@link NoopStats#newNoopViewManager} are in {@link
+ * NoopViewManagerTest}
+ */
+@RunWith(JUnit4.class)
+public final class NoopStatsTest {
+ private static final Tag TAG = Tag.create(TagKey.create("key"), TagValue.create("value"));
+ private static final MeasureDouble MEASURE =
+ Measure.MeasureDouble.create("my measure", "description", "s");
+
+ private final TagContext tagContext =
+ new TagContext() {
+
+ @Override
+ protected Iterator<Tag> getIterator() {
+ return Collections.<Tag>singleton(TAG).iterator();
+ }
+ };
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void noopStatsComponent() {
+ assertThat(NoopStats.newNoopStatsComponent().getStatsRecorder())
+ .isSameAs(NoopStats.getNoopStatsRecorder());
+ assertThat(NoopStats.newNoopStatsComponent().getViewManager())
+ .isInstanceOf(NoopStats.newNoopViewManager().getClass());
+ }
+
+ @Test
+ public void noopStatsComponent_GetState() {
+ assertThat(NoopStats.newNoopStatsComponent().getState())
+ .isEqualTo(StatsCollectionState.DISABLED);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void noopStatsComponent_SetState_IgnoresInput() {
+ StatsComponent noopStatsComponent = NoopStats.newNoopStatsComponent();
+ noopStatsComponent.setState(StatsCollectionState.ENABLED);
+ assertThat(noopStatsComponent.getState()).isEqualTo(StatsCollectionState.DISABLED);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void noopStatsComponent_SetState_DisallowsNull() {
+ StatsComponent noopStatsComponent = NoopStats.newNoopStatsComponent();
+ thrown.expect(NullPointerException.class);
+ noopStatsComponent.setState(null);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void noopStatsComponent_DisallowsSetStateAfterGetState() {
+ StatsComponent noopStatsComponent = NoopStats.newNoopStatsComponent();
+ noopStatsComponent.setState(StatsCollectionState.DISABLED);
+ noopStatsComponent.getState();
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("State was already read, cannot set state.");
+ noopStatsComponent.setState(StatsCollectionState.ENABLED);
+ }
+
+ @Test
+ public void noopStatsRecorder_PutAttachmentNullKey() {
+ MeasureMap measureMap = NoopStats.getNoopStatsRecorder().newMeasureMap();
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("key");
+ measureMap.putAttachment(null, "value");
+ }
+
+ @Test
+ public void noopStatsRecorder_PutAttachmentNullValue() {
+ MeasureMap measureMap = NoopStats.getNoopStatsRecorder().newMeasureMap();
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("value");
+ measureMap.putAttachment("key", null);
+ }
+
+ // The NoopStatsRecorder should do nothing, so this test just checks that record doesn't throw an
+ // exception.
+ @Test
+ public void noopStatsRecorder_Record() {
+ NoopStats.getNoopStatsRecorder().newMeasureMap().put(MEASURE, 5).record(tagContext);
+ }
+
+ // The NoopStatsRecorder should do nothing, so this test just checks that record doesn't throw an
+ // exception.
+ @Test
+ public void noopStatsRecorder_RecordWithCurrentContext() {
+ NoopStats.getNoopStatsRecorder().newMeasureMap().put(MEASURE, 6).record();
+ }
+
+ @Test
+ public void noopStatsRecorder_Record_DisallowNullTagContext() {
+ MeasureMap measureMap = NoopStats.getNoopStatsRecorder().newMeasureMap();
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("tags");
+ measureMap.record(null);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/NoopViewManagerTest.java b/api/src/test/java/io/opencensus/stats/NoopViewManagerTest.java
new file mode 100644
index 00000000..44c7626f
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/NoopViewManagerTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2017, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Duration;
+import io.opencensus.common.Timestamp;
+import io.opencensus.stats.Aggregation.Sum;
+import io.opencensus.stats.Measure.MeasureDouble;
+import io.opencensus.stats.View.AggregationWindow.Cumulative;
+import io.opencensus.stats.View.AggregationWindow.Interval;
+import io.opencensus.stats.View.Name;
+import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData;
+import io.opencensus.stats.ViewData.AggregationWindowData.IntervalData;
+import io.opencensus.tags.TagKey;
+import java.util.Arrays;
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NoopStats#newNoopViewManager}. */
+@RunWith(JUnit4.class)
+public final class NoopViewManagerTest {
+ private static final MeasureDouble MEASURE =
+ Measure.MeasureDouble.create("my measure", "description", "s");
+ private static final TagKey KEY = TagKey.create("KEY");
+ private static final Name VIEW_NAME = Name.create("my view");
+ private static final String VIEW_DESCRIPTION = "view description";
+ private static final Sum AGGREGATION = Sum.create();
+ private static final Cumulative CUMULATIVE = Cumulative.create();
+ private static final Duration TEN_SECONDS = Duration.create(10, 0);
+ private static final Interval INTERVAL = Interval.create(TEN_SECONDS);
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void noopViewManager_RegisterView_DisallowRegisteringDifferentViewWithSameName() {
+ final View view1 =
+ View.create(
+ VIEW_NAME, "description 1", MEASURE, AGGREGATION, Arrays.asList(KEY), CUMULATIVE);
+ final View view2 =
+ View.create(
+ VIEW_NAME, "description 2", MEASURE, AGGREGATION, Arrays.asList(KEY), CUMULATIVE);
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ viewManager.registerView(view1);
+
+ try {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("A different view with the same name already exists.");
+ viewManager.registerView(view2);
+ } finally {
+ assertThat(viewManager.getView(VIEW_NAME).getView()).isEqualTo(view1);
+ }
+ }
+
+ @Test
+ public void noopViewManager_RegisterView_AllowRegisteringSameViewTwice() {
+ View view =
+ View.create(
+ VIEW_NAME, VIEW_DESCRIPTION, MEASURE, AGGREGATION, Arrays.asList(KEY), CUMULATIVE);
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ viewManager.registerView(view);
+ viewManager.registerView(view);
+ }
+
+ @Test
+ public void noopViewManager_RegisterView_DisallowNull() {
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ thrown.expect(NullPointerException.class);
+ viewManager.registerView(null);
+ }
+
+ @Test
+ public void noopViewManager_GetView_GettingNonExistentViewReturnsNull() {
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ assertThat(viewManager.getView(VIEW_NAME)).isNull();
+ }
+
+ @Test
+ public void noopViewManager_GetView_Cumulative() {
+ View view =
+ View.create(
+ VIEW_NAME, VIEW_DESCRIPTION, MEASURE, AGGREGATION, Arrays.asList(KEY), CUMULATIVE);
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ viewManager.registerView(view);
+
+ ViewData viewData = viewManager.getView(VIEW_NAME);
+ assertThat(viewData.getView()).isEqualTo(view);
+ assertThat(viewData.getAggregationMap()).isEmpty();
+ assertThat(viewData.getStart()).isEqualTo(Timestamp.create(0, 0));
+ assertThat(viewData.getEnd()).isEqualTo(Timestamp.create(0, 0));
+ assertThat(viewData.getWindowData())
+ .isEqualTo(CumulativeData.create(Timestamp.create(0, 0), Timestamp.create(0, 0)));
+ }
+
+ @Test
+ public void noopViewManager_GetView_Interval() {
+ View view =
+ View.create(
+ VIEW_NAME, VIEW_DESCRIPTION, MEASURE, AGGREGATION, Arrays.asList(KEY), INTERVAL);
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ viewManager.registerView(view);
+
+ ViewData viewData = viewManager.getView(VIEW_NAME);
+ assertThat(viewData.getView()).isEqualTo(view);
+ assertThat(viewData.getAggregationMap()).isEmpty();
+ assertThat(viewData.getWindowData()).isEqualTo(IntervalData.create(Timestamp.create(0, 0)));
+ }
+
+ @Test
+ public void noopViewManager_GetView_DisallowNull() {
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ thrown.expect(NullPointerException.class);
+ viewManager.getView(null);
+ }
+
+ @Test
+ public void getAllExportedViews() {
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ assertThat(viewManager.getAllExportedViews()).isEmpty();
+ View cumulativeView1 =
+ View.create(
+ View.Name.create("View 1"),
+ VIEW_DESCRIPTION,
+ MEASURE,
+ AGGREGATION,
+ Arrays.asList(KEY),
+ CUMULATIVE);
+ View cumulativeView2 =
+ View.create(
+ View.Name.create("View 2"),
+ VIEW_DESCRIPTION,
+ MEASURE,
+ AGGREGATION,
+ Arrays.asList(KEY),
+ CUMULATIVE);
+ View intervalView =
+ View.create(
+ View.Name.create("View 3"),
+ VIEW_DESCRIPTION,
+ MEASURE,
+ AGGREGATION,
+ Arrays.asList(KEY),
+ INTERVAL);
+ viewManager.registerView(cumulativeView1);
+ viewManager.registerView(cumulativeView2);
+ viewManager.registerView(intervalView);
+
+ // Only cumulative views should be exported.
+ assertThat(viewManager.getAllExportedViews()).containsExactly(cumulativeView1, cumulativeView2);
+ }
+
+ @Test
+ public void getAllExportedViews_ResultIsUnmodifiable() {
+ ViewManager viewManager = NoopStats.newNoopViewManager();
+ View view1 =
+ View.create(
+ View.Name.create("View 1"), VIEW_DESCRIPTION, MEASURE, AGGREGATION, Arrays.asList(KEY));
+ viewManager.registerView(view1);
+ Set<View> exported = viewManager.getAllExportedViews();
+
+ View view2 =
+ View.create(
+ View.Name.create("View 2"), VIEW_DESCRIPTION, MEASURE, AGGREGATION, Arrays.asList(KEY));
+ thrown.expect(UnsupportedOperationException.class);
+ exported.add(view2);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/StatsTest.java b/api/src/test/java/io/opencensus/stats/StatsTest.java
new file mode 100644
index 00000000..4219173a
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/StatsTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Stats}. */
+@RunWith(JUnit4.class)
+public final class StatsTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void loadStatsManager_UsesProvidedClassLoader() {
+ final RuntimeException toThrow = new RuntimeException("UseClassLoader");
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("UseClassLoader");
+ Stats.loadStatsComponent(
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) {
+ throw toThrow;
+ }
+ });
+ }
+
+ @Test
+ public void loadStatsManager_IgnoresMissingClasses() {
+ ClassLoader classLoader =
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException();
+ }
+ };
+
+ assertThat(Stats.loadStatsComponent(classLoader).getClass().getName())
+ .isEqualTo("io.opencensus.stats.NoopStats$NoopStatsComponent");
+ }
+
+ @Test
+ public void defaultValues() {
+ assertThat(Stats.getStatsRecorder()).isEqualTo(NoopStats.getNoopStatsRecorder());
+ assertThat(Stats.getViewManager()).isInstanceOf(NoopStats.newNoopViewManager().getClass());
+ }
+
+ @Test
+ public void getState() {
+ assertThat(Stats.getState()).isEqualTo(StatsCollectionState.DISABLED);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void setState_IgnoresInput() {
+ Stats.setState(StatsCollectionState.ENABLED);
+ assertThat(Stats.getState()).isEqualTo(StatsCollectionState.DISABLED);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void setState_DisallowsNull() {
+ thrown.expect(NullPointerException.class);
+ thrown.expectMessage("state");
+ Stats.setState(null);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/stats/ViewDataTest.java b/api/src/test/java/io/opencensus/stats/ViewDataTest.java
new file mode 100644
index 00000000..0120ffea
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/ViewDataTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Duration;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.stats.Aggregation.Count;
+import io.opencensus.stats.Aggregation.Distribution;
+import io.opencensus.stats.Aggregation.LastValue;
+import io.opencensus.stats.Aggregation.Mean;
+import io.opencensus.stats.Aggregation.Sum;
+import io.opencensus.stats.AggregationData.CountData;
+import io.opencensus.stats.AggregationData.DistributionData;
+import io.opencensus.stats.AggregationData.LastValueDataDouble;
+import io.opencensus.stats.AggregationData.LastValueDataLong;
+import io.opencensus.stats.AggregationData.SumDataDouble;
+import io.opencensus.stats.AggregationData.SumDataLong;
+import io.opencensus.stats.View.AggregationWindow;
+import io.opencensus.stats.View.AggregationWindow.Cumulative;
+import io.opencensus.stats.View.AggregationWindow.Interval;
+import io.opencensus.stats.ViewData.AggregationWindowData;
+import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData;
+import io.opencensus.stats.ViewData.AggregationWindowData.IntervalData;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for class {@link ViewData}. */
+@RunWith(JUnit4.class)
+public final class ViewDataTest {
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testCumulativeViewData() {
+ View view = View.create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, CUMULATIVE);
+ Timestamp start = Timestamp.fromMillis(1000);
+ Timestamp end = Timestamp.fromMillis(2000);
+ AggregationWindowData windowData = CumulativeData.create(start, end);
+ ViewData viewData = ViewData.create(view, ENTRIES, windowData);
+ assertThat(viewData.getView()).isEqualTo(view);
+ assertThat(viewData.getAggregationMap()).isEqualTo(ENTRIES);
+ assertThat(viewData.getWindowData()).isEqualTo(windowData);
+ }
+
+ @Test
+ public void testIntervalViewData() {
+ View view =
+ View.create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, INTERVAL_HOUR);
+ Timestamp end = Timestamp.fromMillis(2000);
+ AggregationWindowData windowData = IntervalData.create(end);
+ ViewData viewData = ViewData.create(view, ENTRIES, windowData);
+ assertThat(viewData.getView()).isEqualTo(view);
+ assertThat(viewData.getAggregationMap()).isEqualTo(ENTRIES);
+ assertThat(viewData.getWindowData()).isEqualTo(windowData);
+ }
+
+ @Test
+ public void testViewDataEquals() {
+ View cumulativeView =
+ View.create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, CUMULATIVE);
+ View intervalView =
+ View.create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, INTERVAL_HOUR);
+
+ new EqualsTester()
+ .addEqualityGroup(
+ ViewData.create(
+ cumulativeView,
+ ENTRIES,
+ CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000))),
+ ViewData.create(
+ cumulativeView,
+ ENTRIES,
+ CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000))))
+ .addEqualityGroup(
+ ViewData.create(
+ cumulativeView,
+ ENTRIES,
+ CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(3000))))
+ .addEqualityGroup(
+ ViewData.create(intervalView, ENTRIES, IntervalData.create(Timestamp.fromMillis(2000))),
+ ViewData.create(intervalView, ENTRIES, IntervalData.create(Timestamp.fromMillis(2000))))
+ .addEqualityGroup(
+ ViewData.create(
+ intervalView,
+ Collections.<List<TagValue>, AggregationData>emptyMap(),
+ IntervalData.create(Timestamp.fromMillis(2000))))
+ .testEquals();
+ }
+
+ @Test
+ public void testAggregationWindowDataMatch() {
+ final Timestamp start = Timestamp.fromMillis(1000);
+ final Timestamp end = Timestamp.fromMillis(2000);
+ final AggregationWindowData windowData1 = CumulativeData.create(start, end);
+ final AggregationWindowData windowData2 = IntervalData.create(end);
+ windowData1.match(
+ new Function<CumulativeData, Void>() {
+ @Override
+ public Void apply(CumulativeData windowData) {
+ assertThat(windowData.getStart()).isEqualTo(start);
+ assertThat(windowData.getEnd()).isEqualTo(end);
+ return null;
+ }
+ },
+ new Function<IntervalData, Void>() {
+ @Override
+ public Void apply(IntervalData windowData) {
+ fail("CumulativeData expected.");
+ return null;
+ }
+ },
+ Functions.<Void>throwIllegalArgumentException());
+ windowData2.match(
+ new Function<CumulativeData, Void>() {
+ @Override
+ public Void apply(CumulativeData windowData) {
+ fail("IntervalData expected.");
+ return null;
+ }
+ },
+ new Function<IntervalData, Void>() {
+ @Override
+ public Void apply(IntervalData windowData) {
+ assertThat(windowData.getEnd()).isEqualTo(end);
+ return null;
+ }
+ },
+ Functions.<Void>throwIllegalArgumentException());
+ }
+
+ @Test
+ public void preventWindowAndAggregationWindowDataMismatch() {
+ CumulativeData cumulativeData =
+ CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000));
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage(
+ "AggregationWindow and AggregationWindowData types mismatch. "
+ + "AggregationWindow: "
+ + INTERVAL_HOUR.getClass().getSimpleName()
+ + " AggregationWindowData: "
+ + cumulativeData.getClass().getSimpleName());
+ ViewData.create(
+ View.create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, INTERVAL_HOUR),
+ ENTRIES,
+ cumulativeData);
+ }
+
+ @Test
+ public void preventWindowAndAggregationWindowDataMismatch2() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("AggregationWindow and AggregationWindowData types mismatch. ");
+ ViewData.create(
+ View.create(NAME, DESCRIPTION, MEASURE_DOUBLE, DISTRIBUTION, TAG_KEYS, CUMULATIVE),
+ ENTRIES,
+ IntervalData.create(Timestamp.fromMillis(1000)));
+ }
+
+ @Test
+ public void preventStartTimeLaterThanEndTime() {
+ thrown.expect(IllegalArgumentException.class);
+ CumulativeData.create(Timestamp.fromMillis(3000), Timestamp.fromMillis(2000));
+ }
+
+ @Test
+ public void preventAggregationAndAggregationDataMismatch_SumDouble_SumLong() {
+ aggregationAndAggregationDataMismatch(
+ createView(Sum.create(), MEASURE_DOUBLE),
+ ImmutableMap.<List<TagValue>, AggregationData>of(
+ Arrays.asList(V1, V2), SumDataLong.create(100)));
+ }
+
+ @Test
+ public void preventAggregationAndAggregationDataMismatch_SumLong_SumDouble() {
+ aggregationAndAggregationDataMismatch(
+ createView(Sum.create(), MEASURE_LONG),
+ ImmutableMap.<List<TagValue>, AggregationData>of(
+ Arrays.asList(V1, V2), SumDataDouble.create(100)));
+ }
+
+ @Test
+ public void preventAggregationAndAggregationDataMismatch_Count_Distribution() {
+ aggregationAndAggregationDataMismatch(createView(Count.create()), ENTRIES);
+ }
+
+ @Test
+ public void preventAggregationAndAggregationDataMismatch_Mean_Distribution() {
+ aggregationAndAggregationDataMismatch(createView(Mean.create()), ENTRIES);
+ }
+
+ @Test
+ public void preventAggregationAndAggregationDataMismatch_Distribution_Count() {
+ aggregationAndAggregationDataMismatch(
+ createView(DISTRIBUTION), ImmutableMap.of(Arrays.asList(V10, V20), CountData.create(100)));
+ }
+
+ @Test
+ public void preventAggregationAndAggregationDataMismatch_LastValueDouble_LastValueLong() {
+ aggregationAndAggregationDataMismatch(
+ createView(LastValue.create(), MEASURE_DOUBLE),
+ ImmutableMap.<List<TagValue>, AggregationData>of(
+ Arrays.asList(V1, V2), LastValueDataLong.create(100)));
+ }
+
+ @Test
+ public void preventAggregationAndAggregationDataMismatch_LastValueLong_LastValueDouble() {
+ aggregationAndAggregationDataMismatch(
+ createView(LastValue.create(), MEASURE_LONG),
+ ImmutableMap.<List<TagValue>, AggregationData>of(
+ Arrays.asList(V1, V2), LastValueDataDouble.create(100)));
+ }
+
+ private static View createView(Aggregation aggregation) {
+ return createView(aggregation, MEASURE_DOUBLE);
+ }
+
+ private static View createView(Aggregation aggregation, Measure measure) {
+ return View.create(NAME, DESCRIPTION, measure, aggregation, TAG_KEYS, CUMULATIVE);
+ }
+
+ private void aggregationAndAggregationDataMismatch(
+ View view, Map<List<TagValue>, ? extends AggregationData> entries) {
+ CumulativeData cumulativeData =
+ CumulativeData.create(Timestamp.fromMillis(1000), Timestamp.fromMillis(2000));
+ Aggregation aggregation = view.getAggregation();
+ AggregationData aggregationData = entries.values().iterator().next();
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage(
+ "Aggregation and AggregationData types mismatch. "
+ + "Aggregation: "
+ + aggregation.getClass().getSimpleName()
+ + " AggregationData: "
+ + aggregationData.getClass().getSimpleName());
+ ViewData.create(view, entries, cumulativeData);
+ }
+
+ // tag keys
+ private static final TagKey K1 = TagKey.create("k1");
+ private static final TagKey K2 = TagKey.create("k2");
+ private static final List<TagKey> TAG_KEYS = Arrays.asList(K1, K2);
+
+ // tag values
+ private static final TagValue V1 = TagValue.create("v1");
+ private static final TagValue V2 = TagValue.create("v2");
+ private static final TagValue V10 = TagValue.create("v10");
+ private static final TagValue V20 = TagValue.create("v20");
+
+ private static final AggregationWindow CUMULATIVE = Cumulative.create();
+ private static final AggregationWindow INTERVAL_HOUR = Interval.create(Duration.create(3600, 0));
+
+ private static final BucketBoundaries BUCKET_BOUNDARIES =
+ BucketBoundaries.create(Arrays.asList(10.0, 20.0, 30.0, 40.0));
+
+ private static final Aggregation DISTRIBUTION = Distribution.create(BUCKET_BOUNDARIES);
+
+ private static final ImmutableMap<List<TagValue>, DistributionData> ENTRIES =
+ ImmutableMap.of(
+ Arrays.asList(V1, V2),
+ DistributionData.create(1, 1, 1, 1, 0, Arrays.asList(0L, 1L, 0L)),
+ Arrays.asList(V10, V20),
+ DistributionData.create(-5, 6, -20, 5, 100.1, Arrays.asList(5L, 0L, 1L)));
+
+ // name
+ private static final View.Name NAME = View.Name.create("test-view");
+ // description
+ private static final String DESCRIPTION = "test-view-descriptor description";
+ // measure
+ private static final Measure MEASURE_DOUBLE =
+ Measure.MeasureDouble.create("measure1", "measure description", "1");
+ private static final Measure MEASURE_LONG =
+ Measure.MeasureLong.create("measure2", "measure description", "1");
+}
diff --git a/api/src/test/java/io/opencensus/stats/ViewTest.java b/api/src/test/java/io/opencensus/stats/ViewTest.java
new file mode 100644
index 00000000..afba1bc0
--- /dev/null
+++ b/api/src/test/java/io/opencensus/stats/ViewTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016-17, 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.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Duration;
+import io.opencensus.stats.Aggregation.Mean;
+import io.opencensus.stats.View.AggregationWindow.Cumulative;
+import io.opencensus.stats.View.AggregationWindow.Interval;
+import io.opencensus.tags.TagKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link View}. */
+@RunWith(JUnit4.class)
+public final class ViewTest {
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testConstants() {
+ assertThat(View.NAME_MAX_LENGTH).isEqualTo(255);
+ }
+
+ @Test
+ public void sortTagKeys() {
+ final View view =
+ View.create(
+ NAME,
+ DESCRIPTION,
+ MEASURE,
+ MEAN,
+ Arrays.asList(
+ TagKey.create("ab"), TagKey.create("a"), TagKey.create("A"), TagKey.create("b")));
+ assertThat(view.getColumns())
+ .containsExactly(
+ TagKey.create("A"), TagKey.create("a"), TagKey.create("ab"), TagKey.create("b"))
+ .inOrder();
+ }
+
+ @Test
+ public void testDistributionView() {
+ final View view = View.create(NAME, DESCRIPTION, MEASURE, MEAN, KEYS);
+ assertThat(view.getName()).isEqualTo(NAME);
+ assertThat(view.getDescription()).isEqualTo(DESCRIPTION);
+ assertThat(view.getMeasure().getName()).isEqualTo(MEASURE.getName());
+ assertThat(view.getAggregation()).isEqualTo(MEAN);
+ assertThat(view.getColumns()).containsExactly(BAR, FOO).inOrder();
+ assertThat(view.getWindow()).isEqualTo(Cumulative.create());
+ }
+
+ @Test
+ public void testIntervalView() {
+ final View view = View.create(NAME, DESCRIPTION, MEASURE, MEAN, KEYS, Interval.create(MINUTE));
+ assertThat(view.getName()).isEqualTo(NAME);
+ assertThat(view.getDescription()).isEqualTo(DESCRIPTION);
+ assertThat(view.getMeasure().getName()).isEqualTo(MEASURE.getName());
+ assertThat(view.getAggregation()).isEqualTo(MEAN);
+ assertThat(view.getColumns()).containsExactly(BAR, FOO).inOrder();
+ assertThat(view.getWindow()).isEqualTo(Interval.create(MINUTE));
+ }
+
+ @Test
+ public void testViewEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ View.create(NAME, DESCRIPTION, MEASURE, MEAN, KEYS),
+ View.create(NAME, DESCRIPTION, MEASURE, MEAN, KEYS, Cumulative.create()))
+ .addEqualityGroup(
+ View.create(NAME, DESCRIPTION + 2, MEASURE, MEAN, KEYS, Cumulative.create()))
+ .addEqualityGroup(
+ View.create(NAME, DESCRIPTION, MEASURE, MEAN, KEYS, Interval.create(MINUTE)),
+ View.create(NAME, DESCRIPTION, MEASURE, MEAN, KEYS, Interval.create(MINUTE)))
+ .addEqualityGroup(
+ View.create(NAME, DESCRIPTION, MEASURE, MEAN, KEYS, Interval.create(TWO_MINUTES)))
+ .testEquals();
+ }
+
+ @Test
+ public void preventDuplicateColumns() {
+ TagKey key1 = TagKey.create("duplicate");
+ TagKey key2 = TagKey.create("duplicate");
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Columns have duplicate.");
+ View.create(NAME, DESCRIPTION, MEASURE, MEAN, Arrays.asList(key1, key2));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void preventNullViewName() {
+ View.create(null, DESCRIPTION, MEASURE, MEAN, KEYS);
+ }
+
+ @Test
+ public void preventTooLongViewName() {
+ char[] chars = new char[View.NAME_MAX_LENGTH + 1];
+ Arrays.fill(chars, 'a');
+ String longName = String.valueOf(chars);
+ thrown.expect(IllegalArgumentException.class);
+ View.Name.create(longName);
+ }
+
+ @Test
+ public void preventNonPrintableViewName() {
+ thrown.expect(IllegalArgumentException.class);
+ View.Name.create("\2");
+ }
+
+ @Test
+ public void testViewName() {
+ assertThat(View.Name.create("my name").asString()).isEqualTo("my name");
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void preventNullNameString() {
+ View.Name.create(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void preventNegativeIntervalDuration() {
+ Interval.create(NEG_TEN_SECONDS);
+ }
+
+ @Test
+ public void testViewNameEquals() {
+ new EqualsTester()
+ .addEqualityGroup(View.Name.create("view-1"), View.Name.create("view-1"))
+ .addEqualityGroup(View.Name.create("view-2"))
+ .testEquals();
+ }
+
+ private static final View.Name NAME = View.Name.create("test-view-name");
+ private static final String DESCRIPTION = "test-view-name description";
+ private static final Measure MEASURE =
+ Measure.MeasureDouble.create("measure", "measure description", "1");
+ private static final TagKey FOO = TagKey.create("foo");
+ private static final TagKey BAR = TagKey.create("bar");
+ private static final List<TagKey> KEYS = Collections.unmodifiableList(Arrays.asList(FOO, BAR));
+ private static final Mean MEAN = Mean.create();
+ private static final Duration MINUTE = Duration.create(60, 0);
+ private static final Duration TWO_MINUTES = Duration.create(120, 0);
+ private static final Duration NEG_TEN_SECONDS = Duration.create(-10, 0);
+}
diff --git a/api/src/test/java/io/opencensus/tags/InternalUtilsTest.java b/api/src/test/java/io/opencensus/tags/InternalUtilsTest.java
new file mode 100644
index 00000000..65482de1
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/InternalUtilsTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import java.util.Iterator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link InternalUtils}. */
+@RunWith(JUnit4.class)
+public final class InternalUtilsTest {
+
+ @Test
+ public void getTags() {
+ final Iterator<Tag> iterator =
+ Lists.<Tag>newArrayList(Tag.create(TagKey.create("k"), TagValue.create("v"))).iterator();
+ TagContext ctx =
+ new TagContext() {
+ @Override
+ protected Iterator<Tag> getIterator() {
+ return iterator;
+ }
+ };
+ assertThat(InternalUtils.getTags(ctx)).isSameAs(iterator);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/NoopTagsTest.java b/api/src/test/java/io/opencensus/tags/NoopTagsTest.java
new file mode 100644
index 00000000..db07520e
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/NoopTagsTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import io.opencensus.internal.NoopScope;
+import io.opencensus.tags.propagation.TagContextBinarySerializer;
+import io.opencensus.tags.propagation.TagContextDeserializationException;
+import io.opencensus.tags.propagation.TagContextSerializationException;
+import java.util.Arrays;
+import java.util.Iterator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NoopTags}. */
+@RunWith(JUnit4.class)
+public final class NoopTagsTest {
+ private static final TagKey KEY = TagKey.create("key");
+ private static final TagValue VALUE = TagValue.create("value");
+
+ private static final TagContext TAG_CONTEXT =
+ new TagContext() {
+
+ @Override
+ protected Iterator<Tag> getIterator() {
+ return Arrays.<Tag>asList(Tag.create(KEY, VALUE)).iterator();
+ }
+ };
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void noopTagsComponent() {
+ assertThat(NoopTags.newNoopTagsComponent().getTagger()).isSameAs(NoopTags.getNoopTagger());
+ assertThat(NoopTags.newNoopTagsComponent().getTagPropagationComponent())
+ .isSameAs(NoopTags.getNoopTagPropagationComponent());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void noopTagsComponent_SetState_DisallowsNull() {
+ TagsComponent noopTagsComponent = NoopTags.newNoopTagsComponent();
+ thrown.expect(NullPointerException.class);
+ noopTagsComponent.setState(null);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void preventSettingStateAfterGettingState_DifferentState() {
+ TagsComponent noopTagsComponent = NoopTags.newNoopTagsComponent();
+ noopTagsComponent.setState(TaggingState.DISABLED);
+ noopTagsComponent.getState();
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("State was already read, cannot set state.");
+ noopTagsComponent.setState(TaggingState.ENABLED);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void preventSettingStateAfterGettingState_SameState() {
+ TagsComponent noopTagsComponent = NoopTags.newNoopTagsComponent();
+ noopTagsComponent.setState(TaggingState.DISABLED);
+ noopTagsComponent.getState();
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("State was already read, cannot set state.");
+ noopTagsComponent.setState(TaggingState.DISABLED);
+ }
+
+ @Test
+ public void noopTagger() {
+ Tagger noopTagger = NoopTags.getNoopTagger();
+ assertThat(noopTagger.empty()).isSameAs(NoopTags.getNoopTagContext());
+ assertThat(noopTagger.getCurrentTagContext()).isSameAs(NoopTags.getNoopTagContext());
+ assertThat(noopTagger.emptyBuilder()).isSameAs(NoopTags.getNoopTagContextBuilder());
+ assertThat(noopTagger.toBuilder(TAG_CONTEXT)).isSameAs(NoopTags.getNoopTagContextBuilder());
+ assertThat(noopTagger.currentBuilder()).isSameAs(NoopTags.getNoopTagContextBuilder());
+ assertThat(noopTagger.withTagContext(TAG_CONTEXT)).isSameAs(NoopScope.getInstance());
+ }
+
+ @Test
+ public void noopTagger_ToBuilder_DisallowsNull() {
+ Tagger noopTagger = NoopTags.getNoopTagger();
+ thrown.expect(NullPointerException.class);
+ noopTagger.toBuilder(null);
+ }
+
+ @Test
+ public void noopTagger_WithTagContext_DisallowsNull() {
+ Tagger noopTagger = NoopTags.getNoopTagger();
+ thrown.expect(NullPointerException.class);
+ noopTagger.withTagContext(null);
+ }
+
+ @Test
+ public void noopTagContextBuilder() {
+ assertThat(NoopTags.getNoopTagContextBuilder().build()).isSameAs(NoopTags.getNoopTagContext());
+ assertThat(NoopTags.getNoopTagContextBuilder().put(KEY, VALUE).build())
+ .isSameAs(NoopTags.getNoopTagContext());
+ assertThat(NoopTags.getNoopTagContextBuilder().buildScoped()).isSameAs(NoopScope.getInstance());
+ assertThat(NoopTags.getNoopTagContextBuilder().put(KEY, VALUE).buildScoped())
+ .isSameAs(NoopScope.getInstance());
+ }
+
+ @Test
+ public void noopTagContextBuilder_Put_DisallowsNullKey() {
+ TagContextBuilder noopBuilder = NoopTags.getNoopTagContextBuilder();
+ thrown.expect(NullPointerException.class);
+ noopBuilder.put(null, VALUE);
+ }
+
+ @Test
+ public void noopTagContextBuilder_Put_DisallowsNullValue() {
+ TagContextBuilder noopBuilder = NoopTags.getNoopTagContextBuilder();
+ thrown.expect(NullPointerException.class);
+ noopBuilder.put(KEY, null);
+ }
+
+ @Test
+ public void noopTagContextBuilder_Remove_DisallowsNullKey() {
+ TagContextBuilder noopBuilder = NoopTags.getNoopTagContextBuilder();
+ thrown.expect(NullPointerException.class);
+ noopBuilder.remove(null);
+ }
+
+ @Test
+ public void noopTagContext() {
+ assertThat(Lists.newArrayList(NoopTags.getNoopTagContext().getIterator())).isEmpty();
+ }
+
+ @Test
+ public void noopTagPropagationComponent() {
+ assertThat(NoopTags.getNoopTagPropagationComponent().getBinarySerializer())
+ .isSameAs(NoopTags.getNoopTagContextBinarySerializer());
+ }
+
+ @Test
+ public void noopTagContextBinarySerializer()
+ throws TagContextDeserializationException, TagContextSerializationException {
+ assertThat(NoopTags.getNoopTagContextBinarySerializer().toByteArray(TAG_CONTEXT))
+ .isEqualTo(new byte[0]);
+ assertThat(NoopTags.getNoopTagContextBinarySerializer().fromByteArray(new byte[5]))
+ .isEqualTo(NoopTags.getNoopTagContext());
+ }
+
+ @Test
+ public void noopTagContextBinarySerializer_ToByteArray_DisallowsNull()
+ throws TagContextSerializationException {
+ TagContextBinarySerializer noopSerializer = NoopTags.getNoopTagContextBinarySerializer();
+ thrown.expect(NullPointerException.class);
+ noopSerializer.toByteArray(null);
+ }
+
+ @Test
+ public void noopTagContextBinarySerializer_FromByteArray_DisallowsNull()
+ throws TagContextDeserializationException {
+ TagContextBinarySerializer noopSerializer = NoopTags.getNoopTagContextBinarySerializer();
+ thrown.expect(NullPointerException.class);
+ noopSerializer.fromByteArray(null);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/TagContextTest.java b/api/src/test/java/io/opencensus/tags/TagContextTest.java
new file mode 100644
index 00000000..025c7bae
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/TagContextTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TagContext}. */
+@RunWith(JUnit4.class)
+public final class TagContextTest {
+ private static final Tag TAG1 = Tag.create(TagKey.create("key"), TagValue.create("val"));
+ private static final Tag TAG2 = Tag.create(TagKey.create("key2"), TagValue.create("val"));
+
+ @Test
+ public void equals_IgnoresTagOrderAndTagContextClass() {
+ new EqualsTester()
+ .addEqualityGroup(
+ new SimpleTagContext(TAG1, TAG2),
+ new SimpleTagContext(TAG1, TAG2),
+ new SimpleTagContext(TAG2, TAG1),
+ new TagContext() {
+ @Override
+ protected Iterator<Tag> getIterator() {
+ return Lists.newArrayList(TAG1, TAG2).iterator();
+ }
+ })
+ .testEquals();
+ }
+
+ @Test
+ public void equals_HandlesNullIterator() {
+ new EqualsTester()
+ .addEqualityGroup(
+ new SimpleTagContext((List<Tag>) null),
+ new SimpleTagContext((List<Tag>) null),
+ new SimpleTagContext())
+ .testEquals();
+ }
+
+ @Test
+ public void equals_DoesNotIgnoreNullTags() {
+ new EqualsTester()
+ .addEqualityGroup(new SimpleTagContext(TAG1))
+ .addEqualityGroup(new SimpleTagContext(TAG1, null), new SimpleTagContext(null, TAG1))
+ .addEqualityGroup(new SimpleTagContext(TAG1, null, null))
+ .testEquals();
+ }
+
+ @Test
+ public void equals_DoesNotIgnoreDuplicateTags() {
+ new EqualsTester()
+ .addEqualityGroup(new SimpleTagContext(TAG1))
+ .addEqualityGroup(new SimpleTagContext(TAG1, TAG1))
+ .testEquals();
+ }
+
+ @Test
+ public void testToString() {
+ assertThat(new SimpleTagContext().toString()).isEqualTo("TagContext");
+ assertThat(new SimpleTagContext(TAG1, TAG2).toString()).isEqualTo("TagContext");
+ }
+
+ private static final class SimpleTagContext extends TagContext {
+ @Nullable private final List<Tag> tags;
+
+ SimpleTagContext(Tag... tags) {
+ this(Lists.newArrayList(tags));
+ }
+
+ SimpleTagContext(List<Tag> tags) {
+ this.tags = tags == null ? null : Collections.unmodifiableList(Lists.newArrayList(tags));
+ }
+
+ @Override
+ @Nullable
+ protected Iterator<Tag> getIterator() {
+ return tags == null ? null : tags.iterator();
+ }
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/TagKeyTest.java b/api/src/test/java/io/opencensus/tags/TagKeyTest.java
new file mode 100644
index 00000000..48cf9fd4
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/TagKeyTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TagKey}. */
+@RunWith(JUnit4.class)
+public final class TagKeyTest {
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testMaxLength() {
+ assertThat(TagKey.MAX_LENGTH).isEqualTo(255);
+ }
+
+ @Test
+ public void testGetName() {
+ assertThat(TagKey.create("foo").getName()).isEqualTo("foo");
+ }
+
+ @Test
+ public void create_AllowTagKeyNameWithMaxLength() {
+ char[] chars = new char[TagKey.MAX_LENGTH];
+ Arrays.fill(chars, 'k');
+ String key = new String(chars);
+ assertThat(TagKey.create(key).getName()).isEqualTo(key);
+ }
+
+ @Test
+ public void create_DisallowTagKeyNameOverMaxLength() {
+ char[] chars = new char[TagKey.MAX_LENGTH + 1];
+ Arrays.fill(chars, 'k');
+ String key = new String(chars);
+ thrown.expect(IllegalArgumentException.class);
+ TagKey.create(key);
+ }
+
+ @Test
+ public void create_DisallowUnprintableChars() {
+ thrown.expect(IllegalArgumentException.class);
+ TagKey.create("\2ab\3cd");
+ }
+
+ @Test
+ public void createString_DisallowEmpty() {
+ thrown.expect(IllegalArgumentException.class);
+ TagKey.create("");
+ }
+
+ @Test
+ public void testTagKeyEquals() {
+ new EqualsTester()
+ .addEqualityGroup(TagKey.create("foo"), TagKey.create("foo"))
+ .addEqualityGroup(TagKey.create("bar"))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/TagTest.java b/api/src/test/java/io/opencensus/tags/TagTest.java
new file mode 100644
index 00000000..3c899e65
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/TagTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link Tag}. */
+@RunWith(JUnit4.class)
+public final class TagTest {
+
+ @Test
+ public void testGetKey() {
+ assertThat(Tag.create(TagKey.create("k"), TagValue.create("v")).getKey())
+ .isEqualTo(TagKey.create("k"));
+ }
+
+ @Test
+ public void testTagEquals() {
+ new EqualsTester()
+ .addEqualityGroup(
+ Tag.create(TagKey.create("Key"), TagValue.create("foo")),
+ Tag.create(TagKey.create("Key"), TagValue.create("foo")))
+ .addEqualityGroup(Tag.create(TagKey.create("Key"), TagValue.create("bar")))
+ .addEqualityGroup(Tag.create(TagKey.create("Key2"), TagValue.create("foo")))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/TagValueTest.java b/api/src/test/java/io/opencensus/tags/TagValueTest.java
new file mode 100644
index 00000000..9aa42c8c
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/TagValueTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TagValue}. */
+@RunWith(JUnit4.class)
+public final class TagValueTest {
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void testMaxLength() {
+ assertThat(TagValue.MAX_LENGTH).isEqualTo(255);
+ }
+
+ @Test
+ public void testAsString() {
+ assertThat(TagValue.create("foo").asString()).isEqualTo("foo");
+ }
+
+ @Test
+ public void create_AllowTagValueWithMaxLength() {
+ char[] chars = new char[TagValue.MAX_LENGTH];
+ Arrays.fill(chars, 'v');
+ String value = new String(chars);
+ assertThat(TagValue.create(value).asString()).isEqualTo(value);
+ }
+
+ @Test
+ public void create_DisallowTagValueOverMaxLength() {
+ char[] chars = new char[TagValue.MAX_LENGTH + 1];
+ Arrays.fill(chars, 'v');
+ String value = new String(chars);
+ thrown.expect(IllegalArgumentException.class);
+ TagValue.create(value);
+ }
+
+ @Test
+ public void disallowTagValueWithUnprintableChars() {
+ String value = "\2ab\3cd";
+ thrown.expect(IllegalArgumentException.class);
+ TagValue.create(value);
+ }
+
+ @Test
+ public void testTagValueEquals() {
+ new EqualsTester()
+ .addEqualityGroup(TagValue.create("foo"), TagValue.create("foo"))
+ .addEqualityGroup(TagValue.create("bar"))
+ .testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/TagsTest.java b/api/src/test/java/io/opencensus/tags/TagsTest.java
new file mode 100644
index 00000000..dee517b6
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/TagsTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017, 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.tags;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Tags}. */
+@RunWith(JUnit4.class)
+public class TagsTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void loadTagsComponent_UsesProvidedClassLoader() {
+ final RuntimeException toThrow = new RuntimeException("UseClassLoader");
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("UseClassLoader");
+ Tags.loadTagsComponent(
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) {
+ throw toThrow;
+ }
+ });
+ }
+
+ @Test
+ public void loadTagsComponent_IgnoresMissingClasses() {
+ ClassLoader classLoader =
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException();
+ }
+ };
+ assertThat(Tags.loadTagsComponent(classLoader).getClass().getName())
+ .isEqualTo("io.opencensus.tags.NoopTags$NoopTagsComponent");
+ }
+
+ // There is only one test that modifies tagging state in the Tags class, since the state is
+ // global, and it could affect other tests. NoopTagsTest has more thorough tests for tagging
+ // state.
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testState() {
+ // Test that setState ignores its input.
+ Tags.setState(TaggingState.ENABLED);
+ assertThat(Tags.getState()).isEqualTo(TaggingState.DISABLED);
+
+ // Test that setState cannot be called after getState.
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("State was already read, cannot set state.");
+ Tags.setState(TaggingState.ENABLED);
+ }
+
+ @Test
+ public void defaultTagger() {
+ assertThat(Tags.getTagger()).isEqualTo(NoopTags.getNoopTagger());
+ }
+
+ @Test
+ public void defaultTagContextSerializer() {
+ assertThat(Tags.getTagPropagationComponent())
+ .isEqualTo(NoopTags.getNoopTagPropagationComponent());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/propagation/TagContextDeserializationExceptionTest.java b/api/src/test/java/io/opencensus/tags/propagation/TagContextDeserializationExceptionTest.java
new file mode 100644
index 00000000..750d5d45
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/propagation/TagContextDeserializationExceptionTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017, 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.tags.propagation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TagContextDeserializationException}. */
+@RunWith(JUnit4.class)
+public final class TagContextDeserializationExceptionTest {
+
+ @Test
+ public void createWithMessage() {
+ assertThat(new TagContextDeserializationException("my message").getMessage())
+ .isEqualTo("my message");
+ }
+
+ @Test
+ public void createWithMessageAndCause() {
+ IOException cause = new IOException();
+ TagContextDeserializationException exception =
+ new TagContextDeserializationException("my message", cause);
+ assertThat(exception.getMessage()).isEqualTo("my message");
+ assertThat(exception.getCause()).isEqualTo(cause);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/propagation/TagContextSerializationExceptionTest.java b/api/src/test/java/io/opencensus/tags/propagation/TagContextSerializationExceptionTest.java
new file mode 100644
index 00000000..54e9fab6
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/propagation/TagContextSerializationExceptionTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017, 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.tags.propagation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TagContextSerializationException}. */
+@RunWith(JUnit4.class)
+public final class TagContextSerializationExceptionTest {
+
+ @Test
+ public void createWithMessage() {
+ assertThat(new TagContextSerializationException("my message").getMessage())
+ .isEqualTo("my message");
+ }
+
+ @Test
+ public void createWithMessageAndCause() {
+ IOException cause = new IOException();
+ TagContextSerializationException exception =
+ new TagContextSerializationException("my message", cause);
+ assertThat(exception.getMessage()).isEqualTo("my message");
+ assertThat(exception.getCause()).isEqualTo(cause);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/tags/unsafe/ContextUtilsTest.java b/api/src/test/java/io/opencensus/tags/unsafe/ContextUtilsTest.java
new file mode 100644
index 00000000..c35c5dc4
--- /dev/null
+++ b/api/src/test/java/io/opencensus/tags/unsafe/ContextUtilsTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017, 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.tags.unsafe;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import io.grpc.Context;
+import io.opencensus.tags.InternalUtils;
+import io.opencensus.tags.Tag;
+import io.opencensus.tags.TagContext;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ContextUtils}. */
+@RunWith(JUnit4.class)
+public final class ContextUtilsTest {
+ @Test
+ public void testContextKeyName() {
+ // Context.Key.toString() returns the name.
+ assertThat(ContextUtils.TAG_CONTEXT_KEY.toString()).isEqualTo("opencensus-tag-context-key");
+ }
+
+ @Test
+ public void testGetCurrentTagContext_DefaultContext() {
+ TagContext tags = ContextUtils.TAG_CONTEXT_KEY.get();
+ assertThat(tags).isNotNull();
+ assertThat(asList(tags)).isEmpty();
+ }
+
+ @Test
+ public void testGetCurrentTagContext_ContextSetToNull() {
+ Context orig = Context.current().withValue(ContextUtils.TAG_CONTEXT_KEY, null).attach();
+ try {
+ TagContext tags = ContextUtils.TAG_CONTEXT_KEY.get();
+ assertThat(tags).isNotNull();
+ assertThat(asList(tags)).isEmpty();
+ } finally {
+ Context.current().detach(orig);
+ }
+ }
+
+ private static List<Tag> asList(TagContext tags) {
+ return Lists.newArrayList(InternalUtils.getTags(tags));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/AnnotationTest.java b/api/src/test/java/io/opencensus/trace/AnnotationTest.java
new file mode 100644
index 00000000..0db5d93c
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/AnnotationTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Link}. */
+@RunWith(JUnit4.class)
+public class AnnotationTest {
+ @Test(expected = NullPointerException.class)
+ public void fromDescription_NullDescription() {
+ Annotation.fromDescription(null);
+ }
+
+ @Test
+ public void fromDescription() {
+ Annotation annotation = Annotation.fromDescription("MyAnnotationText");
+ assertThat(annotation.getDescription()).isEqualTo("MyAnnotationText");
+ assertThat(annotation.getAttributes().size()).isEqualTo(0);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromDescriptionAndAttributes_NullDescription() {
+ Annotation.fromDescriptionAndAttributes(null, Collections.<String, AttributeValue>emptyMap());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromDescriptionAndAttributes_NullAttributes() {
+ Annotation.fromDescriptionAndAttributes("", null);
+ }
+
+ @Test
+ public void fromDescriptionAndAttributes() {
+ Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>();
+ attributes.put(
+ "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue"));
+ Annotation annotation = Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes);
+ assertThat(annotation.getDescription()).isEqualTo("MyAnnotationText");
+ assertThat(annotation.getAttributes()).isEqualTo(attributes);
+ }
+
+ @Test
+ public void fromDescriptionAndAttributes_EmptyAttributes() {
+ Annotation annotation =
+ Annotation.fromDescriptionAndAttributes(
+ "MyAnnotationText", Collections.<String, AttributeValue>emptyMap());
+ assertThat(annotation.getDescription()).isEqualTo("MyAnnotationText");
+ assertThat(annotation.getAttributes().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void annotation_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>();
+ attributes.put(
+ "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue"));
+ tester
+ .addEqualityGroup(
+ Annotation.fromDescription("MyAnnotationText"),
+ Annotation.fromDescriptionAndAttributes(
+ "MyAnnotationText", Collections.<String, AttributeValue>emptyMap()))
+ .addEqualityGroup(
+ Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes),
+ Annotation.fromDescriptionAndAttributes("MyAnnotationText", attributes))
+ .addEqualityGroup(Annotation.fromDescription("MyAnnotationText2"));
+ tester.testEquals();
+ }
+
+ @Test
+ public void annotation_ToString() {
+ Annotation annotation = Annotation.fromDescription("MyAnnotationText");
+ assertThat(annotation.toString()).contains("MyAnnotationText");
+ Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>();
+ attributes.put(
+ "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue"));
+ annotation = Annotation.fromDescriptionAndAttributes("MyAnnotationText2", attributes);
+ assertThat(annotation.toString()).contains("MyAnnotationText2");
+ assertThat(annotation.toString()).contains(attributes.toString());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/AttributeValueTest.java b/api/src/test/java/io/opencensus/trace/AttributeValueTest.java
new file mode 100644
index 00000000..05ef43c0
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/AttributeValueTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AttributeValue}. */
+@RunWith(JUnit4.class)
+public class AttributeValueTest {
+ @Test
+ public void stringAttributeValue() {
+ AttributeValue attribute = AttributeValue.stringAttributeValue("MyStringAttributeValue");
+ attribute.match(
+ new Function<String, Object>() {
+ @Override
+ @Nullable
+ public Object apply(String stringValue) {
+ assertThat(stringValue).isEqualTo("MyStringAttributeValue");
+ return null;
+ }
+ },
+ new Function<Boolean, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Boolean booleanValue) {
+ fail("Expected a String");
+ return null;
+ }
+ },
+ new Function<Long, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Long longValue) {
+ fail("Expected a String");
+ return null;
+ }
+ },
+ Functions.throwIllegalArgumentException());
+ }
+
+ @Test
+ public void booleanAttributeValue() {
+ AttributeValue attribute = AttributeValue.booleanAttributeValue(true);
+ attribute.match(
+ new Function<String, Object>() {
+ @Override
+ @Nullable
+ public Object apply(String stringValue) {
+ fail("Expected a Boolean");
+ return null;
+ }
+ },
+ new Function<Boolean, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Boolean booleanValue) {
+ assertThat(booleanValue).isTrue();
+ return null;
+ }
+ },
+ new Function<Long, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Long longValue) {
+ fail("Expected a Boolean");
+ return null;
+ }
+ },
+ Functions.throwIllegalArgumentException());
+ }
+
+ @Test
+ public void longAttributeValue() {
+ AttributeValue attribute = AttributeValue.longAttributeValue(123456L);
+ attribute.match(
+ new Function<String, Object>() {
+ @Override
+ @Nullable
+ public Object apply(String stringValue) {
+ fail("Expected a Long");
+ return null;
+ }
+ },
+ new Function<Boolean, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Boolean booleanValue) {
+ fail("Expected a Long");
+ return null;
+ }
+ },
+ new Function<Long, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Long longValue) {
+ assertThat(longValue).isEqualTo(123456L);
+ return null;
+ }
+ },
+ Functions.throwIllegalArgumentException());
+ }
+
+ @Test
+ public void doubleAttributeValue() {
+ AttributeValue attribute = AttributeValue.doubleAttributeValue(1.23456);
+ attribute.match(
+ new Function<String, Object>() {
+ @Override
+ @Nullable
+ public Object apply(String stringValue) {
+ fail("Expected a Double");
+ return null;
+ }
+ },
+ new Function<Boolean, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Boolean booleanValue) {
+ fail("Expected a Double");
+ return null;
+ }
+ },
+ new Function<Long, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Long longValue) {
+ fail("Expected a Double");
+ return null;
+ }
+ },
+ new Function<Double, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Double doubleValue) {
+ assertThat(doubleValue).isEqualTo(1.23456);
+ return null;
+ }
+ },
+ Functions.throwIllegalArgumentException());
+ }
+
+ @Test
+ public void doubleAttributeValue_DeprecatedMatchFunction() {
+ AttributeValue attribute = AttributeValue.doubleAttributeValue(1.23456);
+ attribute.match(
+ new Function<String, Object>() {
+ @Override
+ @Nullable
+ public Object apply(String stringValue) {
+ fail("Expected a Double");
+ return null;
+ }
+ },
+ new Function<Boolean, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Boolean booleanValue) {
+ fail("Expected a Double");
+ return null;
+ }
+ },
+ new Function<Long, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Long longValue) {
+ fail("Expected a Double");
+ return null;
+ }
+ },
+ new Function<Object, Object>() {
+ @Override
+ @Nullable
+ public Object apply(Object value) {
+ assertThat(value).isEqualTo(1.23456);
+ return null;
+ }
+ });
+ }
+
+ @Test
+ public void attributeValue_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(
+ AttributeValue.stringAttributeValue("MyStringAttributeValue"),
+ AttributeValue.stringAttributeValue("MyStringAttributeValue"));
+ tester.addEqualityGroup(AttributeValue.stringAttributeValue("MyStringAttributeDiffValue"));
+ tester.addEqualityGroup(
+ AttributeValue.booleanAttributeValue(true), AttributeValue.booleanAttributeValue(true));
+ tester.addEqualityGroup(AttributeValue.booleanAttributeValue(false));
+ tester.addEqualityGroup(
+ AttributeValue.longAttributeValue(123456L), AttributeValue.longAttributeValue(123456L));
+ tester.addEqualityGroup(AttributeValue.longAttributeValue(1234567L));
+ tester.addEqualityGroup(
+ AttributeValue.doubleAttributeValue(1.23456), AttributeValue.doubleAttributeValue(1.23456));
+ tester.addEqualityGroup(AttributeValue.doubleAttributeValue(1.234567));
+ tester.testEquals();
+ }
+
+ @Test
+ public void attributeValue_ToString() {
+ AttributeValue attribute = AttributeValue.stringAttributeValue("MyStringAttributeValue");
+ assertThat(attribute.toString()).contains("MyStringAttributeValue");
+ attribute = AttributeValue.booleanAttributeValue(true);
+ assertThat(attribute.toString()).contains("true");
+ attribute = AttributeValue.longAttributeValue(123456L);
+ assertThat(attribute.toString()).contains("123456");
+ attribute = AttributeValue.doubleAttributeValue(1.23456);
+ assertThat(attribute.toString()).contains("1.23456");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/BlankSpanTest.java b/api/src/test/java/io/opencensus/trace/BlankSpanTest.java
new file mode 100644
index 00000000..185a5acd
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/BlankSpanTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BlankSpan}. */
+@RunWith(JUnit4.class)
+public class BlankSpanTest {
+ @Test
+ public void hasInvalidContextAndDefaultSpanOptions() {
+ assertThat(BlankSpan.INSTANCE.getContext()).isEqualTo(SpanContext.INVALID);
+ assertThat(BlankSpan.INSTANCE.getOptions().isEmpty()).isTrue();
+ }
+
+ @Test
+ public void doNotCrash() {
+ Map<String, AttributeValue> attributes = new HashMap<String, AttributeValue>();
+ attributes.put(
+ "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue"));
+ Map<String, AttributeValue> multipleAttributes = new HashMap<String, AttributeValue>();
+ multipleAttributes.put(
+ "MyStringAttributeKey", AttributeValue.stringAttributeValue("MyStringAttributeValue"));
+ multipleAttributes.put("MyBooleanAttributeKey", AttributeValue.booleanAttributeValue(true));
+ multipleAttributes.put("MyLongAttributeKey", AttributeValue.longAttributeValue(123));
+ // Tests only that all the methods are not crashing/throwing errors.
+ BlankSpan.INSTANCE.putAttribute(
+ "MyStringAttributeKey2", AttributeValue.stringAttributeValue("MyStringAttributeValue2"));
+ BlankSpan.INSTANCE.addAttributes(attributes);
+ BlankSpan.INSTANCE.addAttributes(multipleAttributes);
+ BlankSpan.INSTANCE.addAnnotation("MyAnnotation");
+ BlankSpan.INSTANCE.addAnnotation("MyAnnotation", attributes);
+ BlankSpan.INSTANCE.addAnnotation("MyAnnotation", multipleAttributes);
+ BlankSpan.INSTANCE.addAnnotation(Annotation.fromDescription("MyAnnotation"));
+ BlankSpan.INSTANCE.addNetworkEvent(NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).build());
+ BlankSpan.INSTANCE.addMessageEvent(MessageEvent.builder(MessageEvent.Type.SENT, 1L).build());
+ BlankSpan.INSTANCE.addLink(
+ Link.fromSpanContext(SpanContext.INVALID, Link.Type.CHILD_LINKED_SPAN));
+ BlankSpan.INSTANCE.setStatus(Status.OK);
+ BlankSpan.INSTANCE.end(EndSpanOptions.DEFAULT);
+ BlankSpan.INSTANCE.end();
+ }
+
+ @Test
+ public void blankSpan_ToString() {
+ assertThat(BlankSpan.INSTANCE.toString()).isEqualTo("BlankSpan");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/CurrentSpanUtilsTest.java b/api/src/test/java/io/opencensus/trace/CurrentSpanUtilsTest.java
new file mode 100644
index 00000000..6b16c3d0
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/CurrentSpanUtilsTest.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import io.grpc.Context;
+import io.opencensus.common.Scope;
+import io.opencensus.trace.unsafe.ContextUtils;
+import java.util.concurrent.Callable;
+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 CurrentSpanUtils}. */
+@RunWith(JUnit4.class)
+public class CurrentSpanUtilsTest {
+ @Mock private Span span;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ // TODO(bdrutu): When update to junit 4.13 use assertThrows instead of this.
+ private void executeRunnableAndExpectError(Runnable runnable, Throwable error) {
+ boolean called = false;
+ try {
+ CurrentSpanUtils.withSpan(span, true, runnable).run();
+ } catch (Throwable e) {
+ assertThat(e).isEqualTo(error);
+ called = true;
+ }
+ assertThat(called).isTrue();
+ }
+
+ // TODO(bdrutu): When update to junit 4.13 use assertThrows instead of this.
+ private void executeCallableAndExpectError(Callable<Object> callable, Throwable error) {
+ boolean called = false;
+ try {
+ CurrentSpanUtils.withSpan(span, true, callable).call();
+ } catch (Throwable e) {
+ assertThat(e).isEqualTo(error);
+ called = true;
+ }
+ assertThat(called).isTrue();
+ }
+
+ @Test
+ public void getCurrentSpan_WhenNoContext() {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void getCurrentSpan() {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Context origContext = Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach();
+ // Make sure context is detached even if test fails.
+ try {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ } finally {
+ Context.current().detach(origContext);
+ }
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpan_CloseDetaches() {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Scope ws = CurrentSpanUtils.withSpan(span, false);
+ try {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ } finally {
+ ws.close();
+ }
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ verifyZeroInteractions(span);
+ }
+
+ @Test
+ public void withSpan_CloseDetachesAndEndsSpan() {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Scope ss = CurrentSpanUtils.withSpan(span, true);
+ try {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ } finally {
+ ss.close();
+ }
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ verify(span).end(same(EndSpanOptions.DEFAULT));
+ }
+
+ @Test
+ public void withSpanRunnable() {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Runnable runnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ }
+ };
+ CurrentSpanUtils.withSpan(span, false, runnable).run();
+ verifyZeroInteractions(span);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanRunnable_EndSpan() {
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Runnable runnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ }
+ };
+ CurrentSpanUtils.withSpan(span, true, runnable).run();
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanRunnable_WithError() {
+ final AssertionError error = new AssertionError("MyError");
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Runnable runnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ throw error;
+ }
+ };
+ executeRunnableAndExpectError(runnable, error);
+ verify(span).setStatus(Status.UNKNOWN.withDescription("MyError"));
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanRunnable_WithErrorNoMessage() {
+ final AssertionError error = new AssertionError();
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Runnable runnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ throw error;
+ }
+ };
+ executeRunnableAndExpectError(runnable, error);
+ verify(span).setStatus(Status.UNKNOWN.withDescription("AssertionError"));
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanCallable() throws Exception {
+ final Object ret = new Object();
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Callable<Object> callable =
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ return ret;
+ }
+ };
+ assertThat(CurrentSpanUtils.withSpan(span, false, callable).call()).isEqualTo(ret);
+ verifyZeroInteractions(span);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanCallable_EndSpan() throws Exception {
+ final Object ret = new Object();
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Callable<Object> callable =
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ return ret;
+ }
+ };
+ assertThat(CurrentSpanUtils.withSpan(span, true, callable).call()).isEqualTo(ret);
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanCallable_WithException() {
+ final Exception exception = new Exception("MyException");
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Callable<Object> callable =
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ throw exception;
+ }
+ };
+ executeCallableAndExpectError(callable, exception);
+ verify(span).setStatus(Status.UNKNOWN.withDescription("MyException"));
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanCallable_WithExceptionNoMessage() {
+ final Exception exception = new Exception();
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Callable<Object> callable =
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ throw exception;
+ }
+ };
+ executeCallableAndExpectError(callable, exception);
+ verify(span).setStatus(Status.UNKNOWN.withDescription("Exception"));
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanCallable_WithError() {
+ final AssertionError error = new AssertionError("MyError");
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Callable<Object> callable =
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ throw error;
+ }
+ };
+ executeCallableAndExpectError(callable, error);
+ verify(span).setStatus(Status.UNKNOWN.withDescription("MyError"));
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+
+ @Test
+ public void withSpanCallable_WithErrorNoMessage() {
+ final AssertionError error = new AssertionError();
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ Callable<Object> callable =
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ // When we run the runnable we will have the span in the current Context.
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isSameAs(span);
+ throw error;
+ }
+ };
+ executeCallableAndExpectError(callable, error);
+ verify(span).setStatus(Status.UNKNOWN.withDescription("AssertionError"));
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(CurrentSpanUtils.getCurrentSpan()).isNull();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/EndSpanOptionsTest.java b/api/src/test/java/io/opencensus/trace/EndSpanOptionsTest.java
new file mode 100644
index 00000000..b6ab8e0e
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/EndSpanOptionsTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link EndSpanOptions}. */
+@RunWith(JUnit4.class)
+public class EndSpanOptionsTest {
+ @Test
+ public void endSpanOptions_DefaultOptions() {
+ assertThat(EndSpanOptions.DEFAULT.getStatus()).isNull();
+ assertThat(EndSpanOptions.DEFAULT.getSampleToLocalSpanStore()).isFalse();
+ }
+
+ @Test
+ public void setStatus_Ok() {
+ EndSpanOptions endSpanOptions = EndSpanOptions.builder().setStatus(Status.OK).build();
+ assertThat(endSpanOptions.getStatus()).isEqualTo(Status.OK);
+ }
+
+ @Test
+ public void setStatus_Error() {
+ EndSpanOptions endSpanOptions =
+ EndSpanOptions.builder()
+ .setStatus(Status.CANCELLED.withDescription("ThisIsAnError"))
+ .build();
+ assertThat(endSpanOptions.getStatus())
+ .isEqualTo(Status.CANCELLED.withDescription("ThisIsAnError"));
+ }
+
+ @Test
+ public void setSampleToLocalSpanStore() {
+ EndSpanOptions endSpanOptions =
+ EndSpanOptions.builder().setSampleToLocalSpanStore(true).build();
+ assertThat(endSpanOptions.getSampleToLocalSpanStore()).isTrue();
+ }
+
+ @Test
+ public void endSpanOptions_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(
+ EndSpanOptions.builder()
+ .setStatus(Status.CANCELLED.withDescription("ThisIsAnError"))
+ .build(),
+ EndSpanOptions.builder()
+ .setStatus(Status.CANCELLED.withDescription("ThisIsAnError"))
+ .build());
+ tester.addEqualityGroup(EndSpanOptions.builder().build(), EndSpanOptions.DEFAULT);
+ tester.testEquals();
+ }
+
+ @Test
+ public void endSpanOptions_ToString() {
+ EndSpanOptions endSpanOptions =
+ EndSpanOptions.builder()
+ .setStatus(Status.CANCELLED.withDescription("ThisIsAnError"))
+ .build();
+ assertThat(endSpanOptions.toString()).contains("ThisIsAnError");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/LinkTest.java b/api/src/test/java/io/opencensus/trace/LinkTest.java
new file mode 100644
index 00000000..5c1ebf5d
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/LinkTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.trace.Link.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Link}. */
+@RunWith(JUnit4.class)
+public class LinkTest {
+ private final Map<String, AttributeValue> attributesMap = new HashMap<String, AttributeValue>();
+ private final Random random = new Random(1234);
+ private final SpanContext spanContext =
+ SpanContext.create(
+ TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT);
+
+ @Before
+ public void setUp() {
+ attributesMap.put("MyAttributeKey0", AttributeValue.stringAttributeValue("MyStringAttribute"));
+ attributesMap.put("MyAttributeKey1", AttributeValue.longAttributeValue(10));
+ attributesMap.put("MyAttributeKey2", AttributeValue.booleanAttributeValue(true));
+ }
+
+ @Test
+ public void fromSpanContext_ChildLink() {
+ Link link = Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN);
+ assertThat(link.getTraceId()).isEqualTo(spanContext.getTraceId());
+ assertThat(link.getSpanId()).isEqualTo(spanContext.getSpanId());
+ assertThat(link.getType()).isEqualTo(Type.CHILD_LINKED_SPAN);
+ }
+
+ @Test
+ public void fromSpanContext_ChildLink_WithAttributes() {
+ Link link = Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN, attributesMap);
+ assertThat(link.getTraceId()).isEqualTo(spanContext.getTraceId());
+ assertThat(link.getSpanId()).isEqualTo(spanContext.getSpanId());
+ assertThat(link.getType()).isEqualTo(Type.CHILD_LINKED_SPAN);
+ assertThat(link.getAttributes()).isEqualTo(attributesMap);
+ }
+
+ @Test
+ public void fromSpanContext_ParentLink() {
+ Link link = Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN);
+ assertThat(link.getTraceId()).isEqualTo(spanContext.getTraceId());
+ assertThat(link.getSpanId()).isEqualTo(spanContext.getSpanId());
+ assertThat(link.getType()).isEqualTo(Type.PARENT_LINKED_SPAN);
+ }
+
+ @Test
+ public void fromSpanContext_ParentLink_WithAttributes() {
+ Link link = Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap);
+ assertThat(link.getTraceId()).isEqualTo(spanContext.getTraceId());
+ assertThat(link.getSpanId()).isEqualTo(spanContext.getSpanId());
+ assertThat(link.getType()).isEqualTo(Type.PARENT_LINKED_SPAN);
+ assertThat(link.getAttributes()).isEqualTo(attributesMap);
+ }
+
+ @Test
+ public void link_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester
+ .addEqualityGroup(
+ Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN),
+ Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN))
+ .addEqualityGroup(
+ Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN),
+ Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN))
+ .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.CHILD_LINKED_SPAN))
+ .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.PARENT_LINKED_SPAN))
+ .addEqualityGroup(
+ Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap),
+ Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap));
+ tester.testEquals();
+ }
+
+ @Test
+ public void link_ToString() {
+ Link link = Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN, attributesMap);
+ assertThat(link.toString()).contains(spanContext.getTraceId().toString());
+ assertThat(link.toString()).contains(spanContext.getSpanId().toString());
+ assertThat(link.toString()).contains("CHILD_LINKED_SPAN");
+ assertThat(link.toString()).contains(attributesMap.toString());
+ link = Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap);
+ assertThat(link.toString()).contains(spanContext.getTraceId().toString());
+ assertThat(link.toString()).contains(spanContext.getSpanId().toString());
+ assertThat(link.toString()).contains("PARENT_LINKED_SPAN");
+ assertThat(link.toString()).contains(attributesMap.toString());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/LowerCaseBase16EncodingTest.java b/api/src/test/java/io/opencensus/trace/LowerCaseBase16EncodingTest.java
new file mode 100644
index 00000000..3444d2b3
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/LowerCaseBase16EncodingTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.nio.charset.Charset;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link io.opencensus.trace.LowerCaseBase16Encoding}. */
+@RunWith(JUnit4.class)
+public class LowerCaseBase16EncodingTest {
+ private static final Charset CHARSET = Charset.forName("UTF-8");
+
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void valid_EncodeDecode() {
+ testEncoding("", "");
+ testEncoding("f", "66");
+ testEncoding("fo", "666f");
+ testEncoding("foo", "666f6f");
+ testEncoding("foob", "666f6f62");
+ testEncoding("fooba", "666f6f6261");
+ testEncoding("foobar", "666f6f626172");
+ }
+
+ @Test
+ public void invalidDecodings_UnrecongnizedCharacters() {
+ // These contain bytes not in the decoding.
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Invalid character g");
+ LowerCaseBase16Encoding.decodeToBytes("efhg");
+ }
+
+ @Test
+ public void invalidDecodings_InvalidInputLength() {
+ // Valid base16 strings always have an even length.
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Invalid input length 3");
+ LowerCaseBase16Encoding.decodeToBytes("abc");
+ }
+
+ @Test
+ public void invalidDecodings_InvalidInputLengthAndCharacter() {
+ // These have a combination of invalid length and unrecognized characters.
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Invalid input length 1");
+ LowerCaseBase16Encoding.decodeToBytes("?");
+ }
+
+ private static void testEncoding(String decoded, String encoded) {
+ testEncodes(decoded, encoded);
+ testDecodes(encoded, decoded);
+ }
+
+ private static void testEncodes(String decoded, String encoded) {
+ assertThat(LowerCaseBase16Encoding.encodeToString(decoded.getBytes(CHARSET)))
+ .isEqualTo(encoded);
+ }
+
+ private static void testDecodes(String encoded, String decoded) {
+ assertThat(LowerCaseBase16Encoding.decodeToBytes(encoded)).isEqualTo(decoded.getBytes(CHARSET));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/MessageEventTest.java b/api/src/test/java/io/opencensus/trace/MessageEventTest.java
new file mode 100644
index 00000000..fde32fe6
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/MessageEventTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link MessageEvent}. */
+@RunWith(JUnit4.class)
+public class MessageEventTest {
+ @Test(expected = NullPointerException.class)
+ public void buildMessageEvent_NullType() {
+ MessageEvent.builder(null, 1L).build();
+ }
+
+ @Test
+ public void buildMessageEvent_WithRequiredFields() {
+ MessageEvent messageEvent = MessageEvent.builder(MessageEvent.Type.SENT, 1L).build();
+ assertThat(messageEvent.getType()).isEqualTo(MessageEvent.Type.SENT);
+ assertThat(messageEvent.getMessageId()).isEqualTo(1L);
+ assertThat(messageEvent.getUncompressedMessageSize()).isEqualTo(0L);
+ }
+
+ @Test
+ public void buildMessageEvent_WithUncompressedMessageSize() {
+ MessageEvent messageEvent =
+ MessageEvent.builder(MessageEvent.Type.SENT, 1L).setUncompressedMessageSize(123L).build();
+ assertThat(messageEvent.getType()).isEqualTo(MessageEvent.Type.SENT);
+ assertThat(messageEvent.getMessageId()).isEqualTo(1L);
+ assertThat(messageEvent.getUncompressedMessageSize()).isEqualTo(123L);
+ }
+
+ @Test
+ public void buildMessageEvent_WithCompressedMessageSize() {
+ MessageEvent messageEvent =
+ MessageEvent.builder(MessageEvent.Type.SENT, 1L).setCompressedMessageSize(123L).build();
+ assertThat(messageEvent.getType()).isEqualTo(MessageEvent.Type.SENT);
+ assertThat(messageEvent.getMessageId()).isEqualTo(1L);
+ assertThat(messageEvent.getCompressedMessageSize()).isEqualTo(123L);
+ }
+
+ @Test
+ public void buildMessageEvent_WithAllValues() {
+ MessageEvent messageEvent =
+ MessageEvent.builder(MessageEvent.Type.RECEIVED, 1L)
+ .setUncompressedMessageSize(123L)
+ .setCompressedMessageSize(63L)
+ .build();
+ assertThat(messageEvent.getType()).isEqualTo(MessageEvent.Type.RECEIVED);
+ assertThat(messageEvent.getMessageId()).isEqualTo(1L);
+ assertThat(messageEvent.getUncompressedMessageSize()).isEqualTo(123L);
+ assertThat(messageEvent.getCompressedMessageSize()).isEqualTo(63L);
+ }
+
+ @Test
+ public void messageEvent_ToString() {
+ MessageEvent messageEvent =
+ MessageEvent.builder(MessageEvent.Type.SENT, 1L)
+ .setUncompressedMessageSize(123L)
+ .setCompressedMessageSize(63L)
+ .build();
+ assertThat(messageEvent.toString()).contains("type=SENT");
+ assertThat(messageEvent.toString()).contains("messageId=1");
+ assertThat(messageEvent.toString()).contains("compressedMessageSize=63");
+ assertThat(messageEvent.toString()).contains("uncompressedMessageSize=123");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/NetworkEventTest.java b/api/src/test/java/io/opencensus/trace/NetworkEventTest.java
new file mode 100644
index 00000000..8c132377
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/NetworkEventTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.common.Timestamp;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NetworkEvent}. */
+@RunWith(JUnit4.class)
+public class NetworkEventTest {
+ @Test(expected = NullPointerException.class)
+ public void buildNetworkEvent_NullType() {
+ NetworkEvent.builder(null, 1L).build();
+ }
+
+ @Test
+ public void buildNetworkEvent_WithRequiredFields() {
+ NetworkEvent networkEvent = NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).build();
+ assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT);
+ assertThat(networkEvent.getMessageId()).isEqualTo(1L);
+ assertThat(networkEvent.getKernelTimestamp()).isNull();
+ assertThat(networkEvent.getUncompressedMessageSize()).isEqualTo(0L);
+ }
+
+ @Test
+ public void buildNetworkEvent_WithTimestamp() {
+ NetworkEvent networkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, 1L)
+ .setKernelTimestamp(Timestamp.fromMillis(123456L))
+ .build();
+ assertThat(networkEvent.getKernelTimestamp()).isEqualTo(Timestamp.fromMillis(123456L));
+ assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT);
+ assertThat(networkEvent.getMessageId()).isEqualTo(1L);
+ assertThat(networkEvent.getUncompressedMessageSize()).isEqualTo(0L);
+ }
+
+ @Test
+ public void buildNetworkEvent_WithUncompressedMessageSize() {
+ NetworkEvent networkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).setUncompressedMessageSize(123L).build();
+ assertThat(networkEvent.getKernelTimestamp()).isNull();
+ assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT);
+ assertThat(networkEvent.getMessageId()).isEqualTo(1L);
+ assertThat(networkEvent.getUncompressedMessageSize()).isEqualTo(123L);
+ assertThat(networkEvent.getMessageSize()).isEqualTo(123L);
+ }
+
+ @Test
+ public void buildNetworkEvent_WithMessageSize() {
+ NetworkEvent networkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).setMessageSize(123L).build();
+ assertThat(networkEvent.getKernelTimestamp()).isNull();
+ assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT);
+ assertThat(networkEvent.getMessageId()).isEqualTo(1L);
+ assertThat(networkEvent.getMessageSize()).isEqualTo(123L);
+ assertThat(networkEvent.getUncompressedMessageSize()).isEqualTo(123L);
+ }
+
+ @Test
+ public void buildNetworkEvent_WithCompressedMessageSize() {
+ NetworkEvent networkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, 1L).setCompressedMessageSize(123L).build();
+ assertThat(networkEvent.getKernelTimestamp()).isNull();
+ assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.SENT);
+ assertThat(networkEvent.getMessageId()).isEqualTo(1L);
+ assertThat(networkEvent.getCompressedMessageSize()).isEqualTo(123L);
+ }
+
+ @Test
+ public void buildNetworkEvent_WithAllValues() {
+ NetworkEvent networkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.RECV, 1L)
+ .setKernelTimestamp(Timestamp.fromMillis(123456L))
+ .setUncompressedMessageSize(123L)
+ .setCompressedMessageSize(63L)
+ .build();
+ assertThat(networkEvent.getKernelTimestamp()).isEqualTo(Timestamp.fromMillis(123456L));
+ assertThat(networkEvent.getType()).isEqualTo(NetworkEvent.Type.RECV);
+ assertThat(networkEvent.getMessageId()).isEqualTo(1L);
+ assertThat(networkEvent.getUncompressedMessageSize()).isEqualTo(123L);
+ // Test that getMessageSize returns same as getUncompressedMessageSize();
+ assertThat(networkEvent.getMessageSize()).isEqualTo(123L);
+ assertThat(networkEvent.getCompressedMessageSize()).isEqualTo(63L);
+ }
+
+ @Test
+ public void networkEvent_ToString() {
+ NetworkEvent networkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, 1L)
+ .setKernelTimestamp(Timestamp.fromMillis(123456L))
+ .setUncompressedMessageSize(123L)
+ .setCompressedMessageSize(63L)
+ .build();
+ assertThat(networkEvent.toString()).contains(Timestamp.fromMillis(123456L).toString());
+ assertThat(networkEvent.toString()).contains("type=SENT");
+ assertThat(networkEvent.toString()).contains("messageId=1");
+ assertThat(networkEvent.toString()).contains("compressedMessageSize=63");
+ assertThat(networkEvent.toString()).contains("uncompressedMessageSize=123");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/NoopSpan.java b/api/src/test/java/io/opencensus/trace/NoopSpan.java
new file mode 100644
index 00000000..a21a8aac
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/NoopSpan.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import io.opencensus.internal.Utils;
+import java.util.EnumSet;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * Class to be used in tests where an implementation for the Span is needed.
+ *
+ * <p>Not final to allow Mockito to "spy" this class.
+ */
+public class NoopSpan extends Span {
+
+ /** Creates a new {@code NoopSpan}. */
+ public NoopSpan(SpanContext context, @Nullable EnumSet<Options> options) {
+ super(Utils.checkNotNull(context, "context"), options);
+ }
+
+ @Override
+ public void putAttributes(Map<String, AttributeValue> attributes) {
+ Utils.checkNotNull(attributes, "attributes");
+ }
+
+ @Override
+ public void addAnnotation(String description, Map<String, AttributeValue> attributes) {
+ Utils.checkNotNull(description, "description");
+ Utils.checkNotNull(attributes, "attributes");
+ }
+
+ @Override
+ public void addAnnotation(Annotation annotation) {
+ Utils.checkNotNull(annotation, "annotation");
+ }
+
+ @Override
+ public void addNetworkEvent(NetworkEvent networkEvent) {}
+
+ @Override
+ public void addMessageEvent(MessageEvent messageEvent) {
+ Utils.checkNotNull(messageEvent, "messageEvent");
+ }
+
+ @Override
+ public void addLink(Link link) {
+ Utils.checkNotNull(link, "link");
+ }
+
+ @Override
+ public void end(EndSpanOptions options) {
+ Utils.checkNotNull(options, "options");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/SpanBuilderTest.java b/api/src/test/java/io/opencensus/trace/SpanBuilderTest.java
new file mode 100644
index 00000000..839c8945
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/SpanBuilderTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.opencensus.common.Scope;
+import io.opencensus.trace.Span.Kind;
+import io.opencensus.trace.samplers.Samplers;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+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 SpanBuilder}. */
+@RunWith(JUnit4.class)
+// Need to suppress warnings for MustBeClosed because Java-6 does not support try-with-resources.
+@SuppressWarnings("MustBeClosedChecker")
+public class SpanBuilderTest {
+ private final Tracer tracer = Tracing.getTracer();
+ @Mock private SpanBuilder spanBuilder;
+ @Mock private Span span;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(spanBuilder.startSpan()).thenReturn(span);
+ }
+
+ @Test
+ public void startScopedSpan() {
+ assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ Scope scope = spanBuilder.startScopedSpan();
+ try {
+ assertThat(tracer.getCurrentSpan()).isSameAs(span);
+ } finally {
+ scope.close();
+ }
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test
+ public void startSpanAndRun() {
+ assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ spanBuilder.startSpanAndRun(
+ new Runnable() {
+ @Override
+ public void run() {
+ assertThat(tracer.getCurrentSpan()).isSameAs(span);
+ }
+ });
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test
+ public void startSpanAndCall() throws Exception {
+ final Object ret = new Object();
+ assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ assertThat(
+ spanBuilder.startSpanAndCall(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ assertThat(tracer.getCurrentSpan()).isSameAs(span);
+ return ret;
+ }
+ }))
+ .isEqualTo(ret);
+ verify(span).end(EndSpanOptions.DEFAULT);
+ assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test
+ public void doNotCrash_NoopImplementation() throws Exception {
+ SpanBuilder spanBuilder = tracer.spanBuilder("MySpanName");
+ spanBuilder.setParentLinks(Collections.<Span>emptyList());
+ spanBuilder.setRecordEvents(true);
+ spanBuilder.setSampler(Samplers.alwaysSample());
+ spanBuilder.setSpanKind(Kind.SERVER);
+ assertThat(spanBuilder.startSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/SpanContextTest.java b/api/src/test/java/io/opencensus/trace/SpanContextTest.java
new file mode 100644
index 00000000..54e188c8
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/SpanContextTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SpanContext}. */
+@RunWith(JUnit4.class)
+public class SpanContextTest {
+ private static final byte[] firstTraceIdBytes =
+ new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'a'};
+ private static final byte[] secondTraceIdBytes =
+ new byte[] {0, 0, 0, 0, 0, 0, 0, '0', 0, 0, 0, 0, 0, 0, 0, 0};
+ private static final byte[] firstSpanIdBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 'a'};
+ private static final byte[] secondSpanIdBytes = new byte[] {'0', 0, 0, 0, 0, 0, 0, 0};
+ private static final Tracestate firstTracestate = Tracestate.builder().set("foo", "bar").build();
+ private static final Tracestate secondTracestate = Tracestate.builder().set("foo", "baz").build();
+ private static final SpanContext first =
+ SpanContext.create(
+ TraceId.fromBytes(firstTraceIdBytes),
+ SpanId.fromBytes(firstSpanIdBytes),
+ TraceOptions.DEFAULT,
+ firstTracestate);
+ private static final SpanContext second =
+ SpanContext.create(
+ TraceId.fromBytes(secondTraceIdBytes),
+ SpanId.fromBytes(secondSpanIdBytes),
+ TraceOptions.builder().setIsSampled(true).build(),
+ secondTracestate);
+
+ @Test
+ public void invalidSpanContext() {
+ assertThat(SpanContext.INVALID.getTraceId()).isEqualTo(TraceId.INVALID);
+ assertThat(SpanContext.INVALID.getSpanId()).isEqualTo(SpanId.INVALID);
+ assertThat(SpanContext.INVALID.getTraceOptions()).isEqualTo(TraceOptions.DEFAULT);
+ }
+
+ @Test
+ public void isValid() {
+ assertThat(SpanContext.INVALID.isValid()).isFalse();
+ assertThat(
+ SpanContext.create(
+ TraceId.fromBytes(firstTraceIdBytes), SpanId.INVALID, TraceOptions.DEFAULT)
+ .isValid())
+ .isFalse();
+ assertThat(
+ SpanContext.create(
+ TraceId.INVALID, SpanId.fromBytes(firstSpanIdBytes), TraceOptions.DEFAULT)
+ .isValid())
+ .isFalse();
+ assertThat(first.isValid()).isTrue();
+ assertThat(second.isValid()).isTrue();
+ }
+
+ @Test
+ public void getTraceId() {
+ assertThat(first.getTraceId()).isEqualTo(TraceId.fromBytes(firstTraceIdBytes));
+ assertThat(second.getTraceId()).isEqualTo(TraceId.fromBytes(secondTraceIdBytes));
+ }
+
+ @Test
+ public void getSpanId() {
+ assertThat(first.getSpanId()).isEqualTo(SpanId.fromBytes(firstSpanIdBytes));
+ assertThat(second.getSpanId()).isEqualTo(SpanId.fromBytes(secondSpanIdBytes));
+ }
+
+ @Test
+ public void getTraceOptions() {
+ assertThat(first.getTraceOptions()).isEqualTo(TraceOptions.DEFAULT);
+ assertThat(second.getTraceOptions())
+ .isEqualTo(TraceOptions.builder().setIsSampled(true).build());
+ }
+
+ @Test
+ public void getTracestate() {
+ assertThat(first.getTracestate()).isEqualTo(firstTracestate);
+ assertThat(second.getTracestate()).isEqualTo(secondTracestate);
+ }
+
+ @Test
+ public void spanContext_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(
+ first,
+ SpanContext.create(
+ TraceId.fromBytes(firstTraceIdBytes),
+ SpanId.fromBytes(firstSpanIdBytes),
+ TraceOptions.DEFAULT),
+ SpanContext.create(
+ TraceId.fromBytes(firstTraceIdBytes),
+ SpanId.fromBytes(firstSpanIdBytes),
+ TraceOptions.builder().setIsSampled(false).build(),
+ firstTracestate));
+ tester.addEqualityGroup(
+ second,
+ SpanContext.create(
+ TraceId.fromBytes(secondTraceIdBytes),
+ SpanId.fromBytes(secondSpanIdBytes),
+ TraceOptions.builder().setIsSampled(true).build(),
+ secondTracestate));
+ tester.testEquals();
+ }
+
+ @Test
+ public void spanContext_ToString() {
+ assertThat(first.toString()).contains(TraceId.fromBytes(firstTraceIdBytes).toString());
+ assertThat(first.toString()).contains(SpanId.fromBytes(firstSpanIdBytes).toString());
+ assertThat(first.toString()).contains(TraceOptions.DEFAULT.toString());
+ assertThat(second.toString()).contains(TraceId.fromBytes(secondTraceIdBytes).toString());
+ assertThat(second.toString()).contains(SpanId.fromBytes(secondSpanIdBytes).toString());
+ assertThat(second.toString())
+ .contains(TraceOptions.builder().setIsSampled(true).build().toString());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/SpanIdTest.java b/api/src/test/java/io/opencensus/trace/SpanIdTest.java
new file mode 100644
index 00000000..4a5bc2ae
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/SpanIdTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SpanId}. */
+@RunWith(JUnit4.class)
+public class SpanIdTest {
+ private static final byte[] firstBytes = new byte[] {0, 0, 0, 0, 0, 0, 0, 'a'};
+ private static final byte[] secondBytes = new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 'A'};
+ private static final SpanId first = SpanId.fromBytes(firstBytes);
+ private static final SpanId second = SpanId.fromBytes(secondBytes);
+
+ @Test
+ public void invalidSpanId() {
+ assertThat(SpanId.INVALID.getBytes()).isEqualTo(new byte[8]);
+ }
+
+ @Test
+ public void isValid() {
+ assertThat(SpanId.INVALID.isValid()).isFalse();
+ assertThat(first.isValid()).isTrue();
+ assertThat(second.isValid()).isTrue();
+ }
+
+ @Test
+ public void fromLowerBase16() {
+ assertThat(SpanId.fromLowerBase16("0000000000000000")).isEqualTo(SpanId.INVALID);
+ assertThat(SpanId.fromLowerBase16("0000000000000061")).isEqualTo(first);
+ assertThat(SpanId.fromLowerBase16("ff00000000000041")).isEqualTo(second);
+ }
+
+ @Test
+ public void toLowerBase16() {
+ assertThat(SpanId.INVALID.toLowerBase16()).isEqualTo("0000000000000000");
+ assertThat(first.toLowerBase16()).isEqualTo("0000000000000061");
+ assertThat(second.toLowerBase16()).isEqualTo("ff00000000000041");
+ }
+
+ @Test
+ public void getBytes() {
+ assertThat(first.getBytes()).isEqualTo(firstBytes);
+ assertThat(second.getBytes()).isEqualTo(secondBytes);
+ }
+
+ @Test
+ public void traceId_CompareTo() {
+ assertThat(first.compareTo(second)).isGreaterThan(0);
+ assertThat(second.compareTo(first)).isLessThan(0);
+ assertThat(first.compareTo(SpanId.fromBytes(firstBytes))).isEqualTo(0);
+ }
+
+ @Test
+ public void traceId_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(SpanId.INVALID, SpanId.INVALID);
+ tester.addEqualityGroup(first, SpanId.fromBytes(Arrays.copyOf(firstBytes, firstBytes.length)));
+ tester.addEqualityGroup(
+ second, SpanId.fromBytes(Arrays.copyOf(secondBytes, secondBytes.length)));
+ tester.testEquals();
+ }
+
+ @Test
+ public void traceId_ToString() {
+ assertThat(SpanId.INVALID.toString()).contains("0000000000000000");
+ assertThat(first.toString()).contains("0000000000000061");
+ assertThat(second.toString()).contains("ff00000000000041");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/SpanTest.java b/api/src/test/java/io/opencensus/trace/SpanTest.java
new file mode 100644
index 00000000..f7546ca4
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/SpanTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.verify;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Random;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+/** Unit tests for {@link Span}. */
+@RunWith(JUnit4.class)
+public class SpanTest {
+ private Random random;
+ private SpanContext spanContext;
+ private SpanContext notSampledSpanContext;
+ private EnumSet<Span.Options> spanOptions;
+
+ @Before
+ public void setUp() {
+ random = new Random(1234);
+ spanContext =
+ SpanContext.create(
+ TraceId.generateRandomId(random),
+ SpanId.generateRandomId(random),
+ TraceOptions.builder().setIsSampled(true).build());
+ notSampledSpanContext =
+ SpanContext.create(
+ TraceId.generateRandomId(random),
+ SpanId.generateRandomId(random),
+ TraceOptions.DEFAULT);
+ spanOptions = EnumSet.of(Span.Options.RECORD_EVENTS);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void newSpan_WithNullContext() {
+ new NoopSpan(null, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void newSpan_SampledContextAndNullOptions() {
+ new NoopSpan(spanContext, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void newSpan_SampledContextAndEmptyOptions() {
+ new NoopSpan(spanContext, EnumSet.noneOf(Span.Options.class));
+ }
+
+ @Test
+ public void getOptions_WhenNullOptions() {
+ Span span = new NoopSpan(notSampledSpanContext, null);
+ assertThat(span.getOptions()).isEmpty();
+ }
+
+ @Test
+ public void getContextAndOptions() {
+ Span span = new NoopSpan(spanContext, spanOptions);
+ assertThat(span.getContext()).isEqualTo(spanContext);
+ assertThat(span.getOptions()).isEqualTo(spanOptions);
+ }
+
+ @Test
+ public void putAttributeCallsAddAttributesByDefault() {
+ Span span = Mockito.spy(new NoopSpan(spanContext, spanOptions));
+ span.putAttribute("MyKey", AttributeValue.booleanAttributeValue(true));
+ span.end();
+ verify(span)
+ .putAttributes(
+ eq(Collections.singletonMap("MyKey", AttributeValue.booleanAttributeValue(true))));
+ }
+
+ @Test
+ public void endCallsEndWithDefaultOptions() {
+ Span span = Mockito.spy(new NoopSpan(spanContext, spanOptions));
+ span.end();
+ verify(span).end(same(EndSpanOptions.DEFAULT));
+ }
+
+ @Test
+ public void addMessageEventDefaultImplementation() {
+ Span mockSpan = Mockito.mock(Span.class);
+ MessageEvent messageEvent =
+ MessageEvent.builder(MessageEvent.Type.SENT, 123)
+ .setUncompressedMessageSize(456)
+ .setCompressedMessageSize(789)
+ .build();
+ NetworkEvent networkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, 123)
+ .setUncompressedMessageSize(456)
+ .setCompressedMessageSize(789)
+ .build();
+ Mockito.doCallRealMethod().when(mockSpan).addMessageEvent(messageEvent);
+ mockSpan.addMessageEvent(messageEvent);
+ verify(mockSpan).addNetworkEvent(eq(networkEvent));
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/StatusTest.java b/api/src/test/java/io/opencensus/trace/StatusTest.java
new file mode 100644
index 00000000..108db2d2
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/StatusTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Status}. */
+@RunWith(JUnit4.class)
+public class StatusTest {
+ @Test
+ public void status_Ok() {
+ assertThat(Status.OK.getCanonicalCode()).isEqualTo(Status.CanonicalCode.OK);
+ assertThat(Status.OK.getDescription()).isNull();
+ assertThat(Status.OK.isOk()).isTrue();
+ }
+
+ @Test
+ public void createStatus_WithDescription() {
+ Status status = Status.UNKNOWN.withDescription("This is an error.");
+ assertThat(status.getCanonicalCode()).isEqualTo(Status.CanonicalCode.UNKNOWN);
+ assertThat(status.getDescription()).isEqualTo("This is an error.");
+ assertThat(status.isOk()).isFalse();
+ }
+
+ @Test
+ public void status_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(Status.OK, Status.OK.withDescription(null));
+ tester.addEqualityGroup(
+ Status.CANCELLED.withDescription("ThisIsAnError"),
+ Status.CANCELLED.withDescription("ThisIsAnError"));
+ tester.addEqualityGroup(Status.UNKNOWN.withDescription("This is an error."));
+ tester.testEquals();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/TraceComponentTest.java b/api/src/test/java/io/opencensus/trace/TraceComponentTest.java
new file mode 100644
index 00000000..1c3f07d5
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/TraceComponentTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.internal.ZeroTimeClock;
+import io.opencensus.trace.config.TraceConfig;
+import io.opencensus.trace.export.ExportComponent;
+import io.opencensus.trace.propagation.PropagationComponent;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TraceComponent}. */
+@RunWith(JUnit4.class)
+public class TraceComponentTest {
+ @Test
+ public void defaultTracer() {
+ assertThat(TraceComponent.newNoopTraceComponent().getTracer()).isSameAs(Tracer.getNoopTracer());
+ }
+
+ @Test
+ public void defaultBinaryPropagationHandler() {
+ assertThat(TraceComponent.newNoopTraceComponent().getPropagationComponent())
+ .isSameAs(PropagationComponent.getNoopPropagationComponent());
+ }
+
+ @Test
+ public void defaultClock() {
+ assertThat(TraceComponent.newNoopTraceComponent().getClock()).isInstanceOf(ZeroTimeClock.class);
+ }
+
+ @Test
+ public void defaultTraceExporter() {
+ assertThat(TraceComponent.newNoopTraceComponent().getExportComponent())
+ .isInstanceOf(ExportComponent.newNoopExportComponent().getClass());
+ }
+
+ @Test
+ public void defaultTraceConfig() {
+ assertThat(TraceComponent.newNoopTraceComponent().getTraceConfig())
+ .isSameAs(TraceConfig.getNoopTraceConfig());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/TraceIdTest.java b/api/src/test/java/io/opencensus/trace/TraceIdTest.java
new file mode 100644
index 00000000..c8b5dc8f
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/TraceIdTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TraceId}. */
+@RunWith(JUnit4.class)
+public class TraceIdTest {
+ private static final byte[] firstBytes =
+ new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'a'};
+ private static final byte[] secondBytes =
+ new byte[] {(byte) 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'A'};
+ private static final TraceId first = TraceId.fromBytes(firstBytes);
+ private static final TraceId second = TraceId.fromBytes(secondBytes);
+
+ @Test
+ public void invalidTraceId() {
+ assertThat(TraceId.INVALID.getBytes()).isEqualTo(new byte[16]);
+ }
+
+ @Test
+ public void isValid() {
+ assertThat(TraceId.INVALID.isValid()).isFalse();
+ assertThat(first.isValid()).isTrue();
+ assertThat(second.isValid()).isTrue();
+ }
+
+ @Test
+ public void getBytes() {
+ assertThat(first.getBytes()).isEqualTo(firstBytes);
+ assertThat(second.getBytes()).isEqualTo(secondBytes);
+ }
+
+ @Test
+ public void fromLowerBase16() {
+ assertThat(TraceId.fromLowerBase16("00000000000000000000000000000000"))
+ .isEqualTo(TraceId.INVALID);
+ assertThat(TraceId.fromLowerBase16("00000000000000000000000000000061")).isEqualTo(first);
+ assertThat(TraceId.fromLowerBase16("ff000000000000000000000000000041")).isEqualTo(second);
+ }
+
+ @Test
+ public void toLowerBase16() {
+ assertThat(TraceId.INVALID.toLowerBase16()).isEqualTo("00000000000000000000000000000000");
+ assertThat(first.toLowerBase16()).isEqualTo("00000000000000000000000000000061");
+ assertThat(second.toLowerBase16()).isEqualTo("ff000000000000000000000000000041");
+ }
+
+ @Test
+ public void traceId_CompareTo() {
+ assertThat(first.compareTo(second)).isGreaterThan(0);
+ assertThat(second.compareTo(first)).isLessThan(0);
+ assertThat(first.compareTo(TraceId.fromBytes(firstBytes))).isEqualTo(0);
+ }
+
+ @Test
+ public void traceId_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(TraceId.INVALID, TraceId.INVALID);
+ tester.addEqualityGroup(first, TraceId.fromBytes(Arrays.copyOf(firstBytes, firstBytes.length)));
+ tester.addEqualityGroup(
+ second, TraceId.fromBytes(Arrays.copyOf(secondBytes, secondBytes.length)));
+ tester.testEquals();
+ }
+
+ @Test
+ public void traceId_ToString() {
+ assertThat(TraceId.INVALID.toString()).contains("00000000000000000000000000000000");
+ assertThat(first.toString()).contains("00000000000000000000000000000061");
+ assertThat(second.toString()).contains("ff000000000000000000000000000041");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/TraceOptionsTest.java b/api/src/test/java/io/opencensus/trace/TraceOptionsTest.java
new file mode 100644
index 00000000..3c46d097
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/TraceOptionsTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TraceOptions}. */
+@RunWith(JUnit4.class)
+public class TraceOptionsTest {
+ private static final byte FIRST_BYTE = (byte) 0xff;
+ private static final byte SECOND_BYTE = 1;
+ private static final byte THIRD_BYTE = 6;
+
+ @Test
+ public void getOptions() {
+ assertThat(TraceOptions.DEFAULT.getOptions()).isEqualTo(0);
+ assertThat(TraceOptions.builder().setIsSampled(false).build().getOptions()).isEqualTo(0);
+ assertThat(TraceOptions.builder().setIsSampled(true).build().getOptions()).isEqualTo(1);
+ assertThat(TraceOptions.builder().setIsSampled(true).setIsSampled(false).build().getOptions())
+ .isEqualTo(0);
+ assertThat(TraceOptions.fromByte(FIRST_BYTE).getOptions()).isEqualTo(-1);
+ assertThat(TraceOptions.fromByte(SECOND_BYTE).getOptions()).isEqualTo(1);
+ assertThat(TraceOptions.fromByte(THIRD_BYTE).getOptions()).isEqualTo(6);
+ }
+
+ @Test
+ public void isSampled() {
+ assertThat(TraceOptions.DEFAULT.isSampled()).isFalse();
+ assertThat(TraceOptions.builder().setIsSampled(true).build().isSampled()).isTrue();
+ }
+
+ @Test
+ public void toFromByte() {
+ assertThat(TraceOptions.fromByte(FIRST_BYTE).getByte()).isEqualTo(FIRST_BYTE);
+ assertThat(TraceOptions.fromByte(SECOND_BYTE).getByte()).isEqualTo(SECOND_BYTE);
+ assertThat(TraceOptions.fromByte(THIRD_BYTE).getByte()).isEqualTo(THIRD_BYTE);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void deprecated_fromBytes() {
+ assertThat(TraceOptions.fromBytes(new byte[] {FIRST_BYTE}).getByte()).isEqualTo(FIRST_BYTE);
+ assertThat(TraceOptions.fromBytes(new byte[] {1, FIRST_BYTE}, 1).getByte())
+ .isEqualTo(FIRST_BYTE);
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void deprecated_getBytes() {
+ assertThat(TraceOptions.fromByte(FIRST_BYTE).getBytes()).isEqualTo(new byte[] {FIRST_BYTE});
+ }
+
+ @Test
+ public void builder_FromOptions() {
+ assertThat(
+ TraceOptions.builder(TraceOptions.fromByte(THIRD_BYTE))
+ .setIsSampled(true)
+ .build()
+ .getOptions())
+ .isEqualTo(6 | 1);
+ }
+
+ @Test
+ public void traceOptions_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(TraceOptions.DEFAULT);
+ tester.addEqualityGroup(
+ TraceOptions.fromByte(SECOND_BYTE), TraceOptions.builder().setIsSampled(true).build());
+ tester.addEqualityGroup(TraceOptions.fromByte(FIRST_BYTE));
+ tester.testEquals();
+ }
+
+ @Test
+ public void traceOptions_ToString() {
+ assertThat(TraceOptions.DEFAULT.toString()).contains("sampled=false");
+ assertThat(TraceOptions.builder().setIsSampled(true).build().toString())
+ .contains("sampled=true");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/TracerTest.java b/api/src/test/java/io/opencensus/trace/TracerTest.java
new file mode 100644
index 00000000..58dd4bbe
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/TracerTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2017, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import io.opencensus.common.Scope;
+import java.util.concurrent.Callable;
+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 Tracer}. */
+@RunWith(JUnit4.class)
+// Need to suppress warnings for MustBeClosed because Java-6 does not support try-with-resources.
+@SuppressWarnings("MustBeClosedChecker")
+public class TracerTest {
+ private static final Tracer noopTracer = Tracer.getNoopTracer();
+ private static final String SPAN_NAME = "MySpanName";
+ @Mock private Tracer tracer;
+ @Mock private SpanBuilder spanBuilder;
+ @Mock private Span span;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void defaultGetCurrentSpan() {
+ assertThat(noopTracer.getCurrentSpan()).isEqualTo(BlankSpan.INSTANCE);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void withSpan_NullSpan() {
+ noopTracer.withSpan(null);
+ }
+
+ @Test
+ public void getCurrentSpan_WithSpan() {
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ Scope ws = noopTracer.withSpan(span);
+ try {
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(span);
+ } finally {
+ ws.close();
+ }
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test
+ public void wrapRunnable() {
+ Runnable runnable;
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ runnable =
+ tracer.withSpan(
+ span,
+ new Runnable() {
+ @Override
+ public void run() {
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(span);
+ }
+ });
+ // When we run the runnable we will have the span in the current Context.
+ runnable.run();
+ verifyZeroInteractions(span);
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test
+ public void wrapCallable() throws Exception {
+ final Object ret = new Object();
+ Callable<Object> callable;
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ callable =
+ tracer.withSpan(
+ span,
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(span);
+ return ret;
+ }
+ });
+ // When we call the callable we will have the span in the current Context.
+ assertThat(callable.call()).isEqualTo(ret);
+ verifyZeroInteractions(span);
+ assertThat(noopTracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void spanBuilderWithName_NullName() {
+ noopTracer.spanBuilder(null);
+ }
+
+ @Test
+ public void defaultSpanBuilderWithName() {
+ assertThat(noopTracer.spanBuilder(SPAN_NAME).startSpan()).isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void spanBuilderWithParentAndName_NullName() {
+ noopTracer.spanBuilderWithExplicitParent(null, null);
+ }
+
+ @Test
+ public void defaultSpanBuilderWithParentAndName() {
+ assertThat(noopTracer.spanBuilderWithExplicitParent(SPAN_NAME, null).startSpan())
+ .isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void spanBuilderWithRemoteParent_NullName() {
+ noopTracer.spanBuilderWithRemoteParent(null, null);
+ }
+
+ @Test
+ public void defaultSpanBuilderWithRemoteParent_NullParent() {
+ assertThat(noopTracer.spanBuilderWithRemoteParent(SPAN_NAME, null).startSpan())
+ .isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test
+ public void defaultSpanBuilderWithRemoteParent() {
+ assertThat(noopTracer.spanBuilderWithRemoteParent(SPAN_NAME, SpanContext.INVALID).startSpan())
+ .isSameAs(BlankSpan.INSTANCE);
+ }
+
+ @Test
+ public void startSpanWithParentFromContext() {
+ Scope ws = tracer.withSpan(span);
+ try {
+ assertThat(tracer.getCurrentSpan()).isSameAs(span);
+ when(tracer.spanBuilderWithExplicitParent(same(SPAN_NAME), same(span)))
+ .thenReturn(spanBuilder);
+ assertThat(tracer.spanBuilder(SPAN_NAME)).isSameAs(spanBuilder);
+ } finally {
+ ws.close();
+ }
+ }
+
+ @Test
+ public void startSpanWithInvalidParentFromContext() {
+ Scope ws = tracer.withSpan(BlankSpan.INSTANCE);
+ try {
+ assertThat(tracer.getCurrentSpan()).isSameAs(BlankSpan.INSTANCE);
+ when(tracer.spanBuilderWithExplicitParent(same(SPAN_NAME), same(BlankSpan.INSTANCE)))
+ .thenReturn(spanBuilder);
+ assertThat(tracer.spanBuilder(SPAN_NAME)).isSameAs(spanBuilder);
+ } finally {
+ ws.close();
+ }
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/TracestateTest.java b/api/src/test/java/io/opencensus/trace/TracestateTest.java
new file mode 100644
index 00000000..3374eb75
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/TracestateTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.trace.Tracestate.Entry;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Tracestate}. */
+@RunWith(JUnit4.class)
+public class TracestateTest {
+ 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";
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ private static final Tracestate EMPTY = Tracestate.builder().build();
+ private final Tracestate firstTracestate = EMPTY.toBuilder().set(FIRST_KEY, FIRST_VALUE).build();
+ private final Tracestate secondTracestate =
+ EMPTY.toBuilder().set(SECOND_KEY, SECOND_VALUE).build();
+ private final Tracestate multiValueTracestate =
+ EMPTY.toBuilder().set(FIRST_KEY, FIRST_VALUE).set(SECOND_KEY, SECOND_VALUE).build();
+
+ @Test
+ public void get() {
+ assertThat(firstTracestate.get(FIRST_KEY)).isEqualTo(FIRST_VALUE);
+ assertThat(secondTracestate.get(SECOND_KEY)).isEqualTo(SECOND_VALUE);
+ assertThat(multiValueTracestate.get(FIRST_KEY)).isEqualTo(FIRST_VALUE);
+ assertThat(multiValueTracestate.get(SECOND_KEY)).isEqualTo(SECOND_VALUE);
+ }
+
+ @Test
+ public void getEntries() {
+ assertThat(firstTracestate.getEntries()).containsExactly(Entry.create(FIRST_KEY, FIRST_VALUE));
+ assertThat(secondTracestate.getEntries())
+ .containsExactly(Entry.create(SECOND_KEY, SECOND_VALUE));
+ assertThat(multiValueTracestate.getEntries())
+ .containsExactly(
+ Entry.create(FIRST_KEY, FIRST_VALUE), Entry.create(SECOND_KEY, SECOND_VALUE));
+ }
+
+ @Test
+ public void disallowsNullKey() {
+ thrown.expect(NullPointerException.class);
+ EMPTY.toBuilder().set(null, FIRST_VALUE).build();
+ }
+
+ @Test
+ public void invalidFirstKeyCharacter() {
+ thrown.expect(IllegalArgumentException.class);
+ EMPTY.toBuilder().set("1_key", FIRST_VALUE).build();
+ }
+
+ @Test
+ public void invalidKeyCharacters() {
+ thrown.expect(IllegalArgumentException.class);
+ EMPTY.toBuilder().set("kEy_1", FIRST_VALUE).build();
+ }
+
+ @Test
+ public void invalidKeySize() {
+ char[] chars = new char[257];
+ Arrays.fill(chars, 'a');
+ String longKey = new String(chars);
+ thrown.expect(IllegalArgumentException.class);
+ EMPTY.toBuilder().set(longKey, FIRST_VALUE).build();
+ }
+
+ @Test
+ public void allAllowedKeyCharacters() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (char c = 'a'; c <= 'z'; c++) {
+ stringBuilder.append(c);
+ }
+ for (char c = '0'; c <= '9'; c++) {
+ stringBuilder.append(c);
+ }
+ stringBuilder.append('_');
+ stringBuilder.append('-');
+ stringBuilder.append('*');
+ stringBuilder.append('/');
+ String allowedKey = stringBuilder.toString();
+ assertThat(EMPTY.toBuilder().set(allowedKey, FIRST_VALUE).build().get(allowedKey))
+ .isEqualTo(FIRST_VALUE);
+ }
+
+ @Test
+ public void disallowsNullValue() {
+ thrown.expect(NullPointerException.class);
+ EMPTY.toBuilder().set(FIRST_KEY, null).build();
+ }
+
+ @Test
+ public void valueCannotContainEqual() {
+ thrown.expect(IllegalArgumentException.class);
+ EMPTY.toBuilder().set(FIRST_KEY, "my_vakue=5").build();
+ }
+
+ @Test
+ public void valueCannotContainComma() {
+ thrown.expect(IllegalArgumentException.class);
+ EMPTY.toBuilder().set(FIRST_KEY, "first,second").build();
+ }
+
+ @Test
+ public void valueCannotContainTrailingSpaces() {
+ thrown.expect(IllegalArgumentException.class);
+ EMPTY.toBuilder().set(FIRST_KEY, "first ").build();
+ }
+
+ @Test
+ public void invalidValueSize() {
+ char[] chars = new char[257];
+ Arrays.fill(chars, 'a');
+ String longValue = new String(chars);
+ thrown.expect(IllegalArgumentException.class);
+ EMPTY.toBuilder().set(FIRST_KEY, longValue).build();
+ }
+
+ @Test
+ public void allAllowedValueCharacters() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (char c = ' ' /* '\u0020' */; c <= '~' /* '\u007E' */; c++) {
+ if (c == ',' || c == '=') {
+ continue;
+ }
+ stringBuilder.append(c);
+ }
+ String allowedValue = stringBuilder.toString();
+ assertThat(EMPTY.toBuilder().set(FIRST_KEY, allowedValue).build().get(FIRST_KEY))
+ .isEqualTo(allowedValue);
+ }
+
+ @Test
+ public void addEntry() {
+ assertThat(firstTracestate.toBuilder().set(SECOND_KEY, SECOND_VALUE).build())
+ .isEqualTo(multiValueTracestate);
+ }
+
+ @Test
+ public void updateEntry() {
+ assertThat(firstTracestate.toBuilder().set(FIRST_KEY, SECOND_VALUE).build().get(FIRST_KEY))
+ .isEqualTo(SECOND_VALUE);
+ Tracestate updatedMultiValueTracestate =
+ multiValueTracestate.toBuilder().set(FIRST_KEY, SECOND_VALUE).build();
+ assertThat(updatedMultiValueTracestate.get(FIRST_KEY)).isEqualTo(SECOND_VALUE);
+ assertThat(updatedMultiValueTracestate.get(SECOND_KEY)).isEqualTo(SECOND_VALUE);
+ }
+
+ @Test
+ public void addAndUpdateEntry() {
+ assertThat(
+ firstTracestate
+ .toBuilder()
+ .set(FIRST_KEY, SECOND_VALUE) // update the existing entry
+ .set(SECOND_KEY, FIRST_VALUE) // add a new entry
+ .build()
+ .getEntries())
+ .containsExactly(
+ Entry.create(FIRST_KEY, SECOND_VALUE), Entry.create(SECOND_KEY, FIRST_VALUE));
+ }
+
+ @Test
+ public void addSameKey() {
+ assertThat(
+ EMPTY
+ .toBuilder()
+ .set(FIRST_KEY, SECOND_VALUE) // update the existing entry
+ .set(FIRST_KEY, FIRST_VALUE) // add a new entry
+ .build()
+ .getEntries())
+ .containsExactly(Entry.create(FIRST_KEY, FIRST_VALUE));
+ }
+
+ @Test
+ public void remove() {
+ assertThat(multiValueTracestate.toBuilder().remove(SECOND_KEY).build())
+ .isEqualTo(firstTracestate);
+ }
+
+ @Test
+ public void addAndRemoveEntry() {
+ assertThat(
+ EMPTY
+ .toBuilder()
+ .set(FIRST_KEY, SECOND_VALUE) // update the existing entry
+ .remove(FIRST_KEY) // add a new entry
+ .build())
+ .isEqualTo(EMPTY);
+ }
+
+ @Test
+ public void remove_NullNotAllowed() {
+ thrown.expect(NullPointerException.class);
+ multiValueTracestate.toBuilder().remove(null).build();
+ }
+
+ @Test
+ public void tracestate_EqualsAndHashCode() {
+ EqualsTester tester = new EqualsTester();
+ tester.addEqualityGroup(EMPTY, EMPTY);
+ tester.addEqualityGroup(firstTracestate, EMPTY.toBuilder().set(FIRST_KEY, FIRST_VALUE).build());
+ tester.addEqualityGroup(
+ secondTracestate, EMPTY.toBuilder().set(SECOND_KEY, SECOND_VALUE).build());
+ tester.testEquals();
+ }
+
+ @Test
+ public void tracestate_ToString() {
+ assertThat(EMPTY.toString()).isEqualTo("Tracestate{entries=[]}");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/TracingTest.java b/api/src/test/java/io/opencensus/trace/TracingTest.java
new file mode 100644
index 00000000..e7c93a95
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/TracingTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016-17, 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.trace;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.config.TraceConfig;
+import io.opencensus.trace.export.ExportComponent;
+import io.opencensus.trace.propagation.PropagationComponent;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Tracing}. */
+@RunWith(JUnit4.class)
+public class TracingTest {
+ @Rule public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void loadTraceComponent_UsesProvidedClassLoader() {
+ final RuntimeException toThrow = new RuntimeException("UseClassLoader");
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("UseClassLoader");
+ Tracing.loadTraceComponent(
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) {
+ throw toThrow;
+ }
+ });
+ }
+
+ @Test
+ public void loadTraceComponent_IgnoresMissingClasses() {
+ ClassLoader classLoader =
+ new ClassLoader() {
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException();
+ }
+ };
+ assertThat(Tracing.loadTraceComponent(classLoader).getClass().getName())
+ .isEqualTo("io.opencensus.trace.TraceComponent$NoopTraceComponent");
+ }
+
+ @Test
+ public void defaultTracer() {
+ assertThat(Tracing.getTracer()).isSameAs(Tracer.getNoopTracer());
+ }
+
+ @Test
+ public void defaultBinaryPropagationHandler() {
+ assertThat(Tracing.getPropagationComponent())
+ .isSameAs(PropagationComponent.getNoopPropagationComponent());
+ }
+
+ @Test
+ public void defaultTraceExporter() {
+ assertThat(Tracing.getExportComponent())
+ .isInstanceOf(ExportComponent.newNoopExportComponent().getClass());
+ }
+
+ @Test
+ public void defaultTraceConfig() {
+ assertThat(Tracing.getTraceConfig()).isSameAs(TraceConfig.getNoopTraceConfig());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/config/TraceConfigTest.java b/api/src/test/java/io/opencensus/trace/config/TraceConfigTest.java
new file mode 100644
index 00000000..d48e0894
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/config/TraceConfigTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017, 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.trace.config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.samplers.Samplers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TraceConfig}. */
+@RunWith(JUnit4.class)
+public class TraceConfigTest {
+ TraceConfig traceConfig = TraceConfig.getNoopTraceConfig();
+
+ @Test
+ public void activeTraceParams_NoOpImplementation() {
+ assertThat(traceConfig.getActiveTraceParams()).isEqualTo(TraceParams.DEFAULT);
+ }
+
+ @Test
+ public void updateActiveTraceParams_NoOpImplementation() {
+ TraceParams traceParams =
+ TraceParams.DEFAULT
+ .toBuilder()
+ .setSampler(Samplers.alwaysSample())
+ .setMaxNumberOfAttributes(8)
+ .setMaxNumberOfAnnotations(9)
+ .setMaxNumberOfNetworkEvents(10)
+ .setMaxNumberOfMessageEvents(10)
+ .setMaxNumberOfLinks(11)
+ .build();
+ traceConfig.updateActiveTraceParams(traceParams);
+ assertThat(traceConfig.getActiveTraceParams()).isEqualTo(TraceParams.DEFAULT);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/config/TraceParamsTest.java b/api/src/test/java/io/opencensus/trace/config/TraceParamsTest.java
new file mode 100644
index 00000000..bdf07d53
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/config/TraceParamsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2017, 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.trace.config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.samplers.Samplers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TraceParams}. */
+@RunWith(JUnit4.class)
+public class TraceParamsTest {
+ @Test
+ public void defaultTraceParams() {
+ assertThat(TraceParams.DEFAULT.getSampler()).isEqualTo(Samplers.probabilitySampler(1e-4));
+ assertThat(TraceParams.DEFAULT.getMaxNumberOfAttributes()).isEqualTo(32);
+ assertThat(TraceParams.DEFAULT.getMaxNumberOfAnnotations()).isEqualTo(32);
+ assertThat(TraceParams.DEFAULT.getMaxNumberOfNetworkEvents()).isEqualTo(128);
+ assertThat(TraceParams.DEFAULT.getMaxNumberOfMessageEvents()).isEqualTo(128);
+ assertThat(TraceParams.DEFAULT.getMaxNumberOfLinks()).isEqualTo(32);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void updateTraceParams_NullSampler() {
+ TraceParams.DEFAULT.toBuilder().setSampler(null).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void updateTraceParams_NonPositiveMaxNumberOfAttributes() {
+ TraceParams.DEFAULT.toBuilder().setMaxNumberOfAttributes(0).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void updateTraceParams_NonPositiveMaxNumberOfAnnotations() {
+ TraceParams.DEFAULT.toBuilder().setMaxNumberOfAnnotations(0).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void updateTraceParams_NonPositiveMaxNumberOfNetworkEvents() {
+ TraceParams.DEFAULT.toBuilder().setMaxNumberOfNetworkEvents(0).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void updateTraceParams_NonPositiveMaxNumberOfMessageEvents() {
+ TraceParams.DEFAULT.toBuilder().setMaxNumberOfMessageEvents(0).build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void updateTraceParams_NonPositiveMaxNumberOfLinks() {
+ TraceParams.DEFAULT.toBuilder().setMaxNumberOfLinks(0).build();
+ }
+
+ @Test
+ public void updateTraceParams_All() {
+ TraceParams traceParams =
+ TraceParams.DEFAULT
+ .toBuilder()
+ .setSampler(Samplers.alwaysSample())
+ .setMaxNumberOfAttributes(8)
+ .setMaxNumberOfAnnotations(9)
+ .setMaxNumberOfMessageEvents(10)
+ .setMaxNumberOfLinks(11)
+ .build();
+ assertThat(traceParams.getSampler()).isEqualTo(Samplers.alwaysSample());
+ assertThat(traceParams.getMaxNumberOfAttributes()).isEqualTo(8);
+ assertThat(traceParams.getMaxNumberOfAnnotations()).isEqualTo(9);
+ // test maxNumberOfNetworkEvent can be set via maxNumberOfMessageEvent
+ assertThat(traceParams.getMaxNumberOfNetworkEvents()).isEqualTo(10);
+ assertThat(traceParams.getMaxNumberOfMessageEvents()).isEqualTo(10);
+ assertThat(traceParams.getMaxNumberOfLinks()).isEqualTo(11);
+ }
+
+ @Test
+ public void updateTraceParams_maxNumberOfNetworkEvents() {
+ TraceParams traceParams =
+ TraceParams.DEFAULT.toBuilder().setMaxNumberOfNetworkEvents(10).build();
+ assertThat(traceParams.getMaxNumberOfNetworkEvents()).isEqualTo(10);
+ // test maxNumberOfMessageEvent can be set via maxNumberOfNetworkEvent
+ assertThat(traceParams.getMaxNumberOfMessageEvents()).isEqualTo(10);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/export/ExportComponentTest.java b/api/src/test/java/io/opencensus/trace/export/ExportComponentTest.java
new file mode 100644
index 00000000..d7f385d0
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/export/ExportComponentTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ExportComponent}. */
+@RunWith(JUnit4.class)
+public class ExportComponentTest {
+ private final ExportComponent exportComponent = ExportComponent.newNoopExportComponent();
+
+ @Test
+ public void implementationOfSpanExporter() {
+ assertThat(exportComponent.getSpanExporter()).isEqualTo(SpanExporter.getNoopSpanExporter());
+ }
+
+ @Test
+ public void implementationOfActiveSpans() {
+ assertThat(exportComponent.getRunningSpanStore())
+ .isEqualTo(RunningSpanStore.getNoopRunningSpanStore());
+ }
+
+ @Test
+ public void implementationOfSampledSpanStore() {
+ assertThat(exportComponent.getSampledSpanStore())
+ .isInstanceOf(SampledSpanStore.newNoopSampledSpanStore().getClass());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/export/NoopRunningSpanStoreTest.java b/api/src/test/java/io/opencensus/trace/export/NoopRunningSpanStoreTest.java
new file mode 100644
index 00000000..960da27c
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/export/NoopRunningSpanStoreTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.Collection;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NoopRunningSpanStore}. */
+@RunWith(JUnit4.class)
+public final class NoopRunningSpanStoreTest {
+
+ private final RunningSpanStore runningSpanStore =
+ ExportComponent.newNoopExportComponent().getRunningSpanStore();
+
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void noopRunningSpanStore_GetSummary() {
+ RunningSpanStore.Summary summary = runningSpanStore.getSummary();
+ assertThat(summary.getPerSpanNameSummary()).isEmpty();
+ }
+
+ @Test
+ public void noopRunningSpanStore_GetRunningSpans_DisallowsNull() {
+ thrown.expect(NullPointerException.class);
+ runningSpanStore.getRunningSpans(null);
+ }
+
+ @Test
+ public void noopRunningSpanStore_GetRunningSpans() {
+ Collection<SpanData> runningSpans =
+ runningSpanStore.getRunningSpans(RunningSpanStore.Filter.create("TestSpan", 0));
+ assertThat(runningSpans).isEmpty();
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/export/NoopSampledSpanStoreTest.java b/api/src/test/java/io/opencensus/trace/export/NoopSampledSpanStoreTest.java
new file mode 100644
index 00000000..6e9c7b0f
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/export/NoopSampledSpanStoreTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import io.opencensus.trace.Status.CanonicalCode;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NoopSampledSpanStore}. */
+@RunWith(JUnit4.class)
+public final class NoopSampledSpanStoreTest {
+
+ private static final SampledSpanStore.PerSpanNameSummary EMPTY_PER_SPAN_NAME_SUMMARY =
+ SampledSpanStore.PerSpanNameSummary.create(
+ Collections.<SampledSpanStore.LatencyBucketBoundaries, Integer>emptyMap(),
+ Collections.<CanonicalCode, Integer>emptyMap());
+
+ @Test
+ public void noopSampledSpanStore_RegisterUnregisterAndGetSummary() {
+ // should return empty before register
+ SampledSpanStore sampledSpanStore =
+ ExportComponent.newNoopExportComponent().getSampledSpanStore();
+ SampledSpanStore.Summary summary = sampledSpanStore.getSummary();
+ assertThat(summary.getPerSpanNameSummary()).isEmpty();
+
+ // should return non-empty summaries with zero latency/error sampled spans after register
+ sampledSpanStore.registerSpanNamesForCollection(
+ Collections.unmodifiableList(Lists.newArrayList("TestSpan1", "TestSpan2", "TestSpan3")));
+ summary = sampledSpanStore.getSummary();
+ assertThat(summary.getPerSpanNameSummary())
+ .containsExactly(
+ "TestSpan1", EMPTY_PER_SPAN_NAME_SUMMARY,
+ "TestSpan2", EMPTY_PER_SPAN_NAME_SUMMARY,
+ "TestSpan3", EMPTY_PER_SPAN_NAME_SUMMARY);
+
+ // should unregister specific spanNames
+ sampledSpanStore.unregisterSpanNamesForCollection(
+ Collections.unmodifiableList(Lists.newArrayList("TestSpan1", "TestSpan3")));
+ summary = sampledSpanStore.getSummary();
+ assertThat(summary.getPerSpanNameSummary())
+ .containsExactly("TestSpan2", EMPTY_PER_SPAN_NAME_SUMMARY);
+ }
+
+ @Test
+ public void noopSampledSpanStore_GetLatencySampledSpans() {
+ SampledSpanStore sampledSpanStore =
+ ExportComponent.newNoopExportComponent().getSampledSpanStore();
+ Collection<SpanData> latencySampledSpans =
+ sampledSpanStore.getLatencySampledSpans(
+ SampledSpanStore.LatencyFilter.create("TestLatencyFilter", 0, 0, 0));
+ assertThat(latencySampledSpans).isEmpty();
+ }
+
+ @Test
+ public void noopSampledSpanStore_GetErrorSampledSpans() {
+ SampledSpanStore sampledSpanStore =
+ ExportComponent.newNoopExportComponent().getSampledSpanStore();
+ Collection<SpanData> errorSampledSpans =
+ sampledSpanStore.getErrorSampledSpans(
+ SampledSpanStore.ErrorFilter.create("TestErrorFilter", null, 0));
+ assertThat(errorSampledSpans).isEmpty();
+ }
+
+ @Test
+ public void noopSampledSpanStore_GetRegisteredSpanNamesForCollection() {
+ SampledSpanStore sampledSpanStore =
+ ExportComponent.newNoopExportComponent().getSampledSpanStore();
+ sampledSpanStore.registerSpanNamesForCollection(
+ Collections.unmodifiableList(Lists.newArrayList("TestSpan3", "TestSpan4")));
+ Set<String> registeredSpanNames = sampledSpanStore.getRegisteredSpanNamesForCollection();
+ assertThat(registeredSpanNames).containsExactly("TestSpan3", "TestSpan4");
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/export/SpanDataTest.java b/api/src/test/java/io/opencensus/trace/export/SpanDataTest.java
new file mode 100644
index 00000000..b991d145
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/export/SpanDataTest.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2017, 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.trace.export;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.opencensus.common.Timestamp;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Link.Type;
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.NetworkEvent;
+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.export.SpanData.Attributes;
+import io.opencensus.trace.export.SpanData.Links;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import io.opencensus.trace.export.SpanData.TimedEvents;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SpanData}. */
+@RunWith(JUnit4.class)
+public class SpanDataTest {
+ 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 SPAN_NAME = "MySpanName";
+ private static final String ANNOTATION_TEXT = "MyAnnotationText";
+ private static final Annotation annotation = Annotation.fromDescription(ANNOTATION_TEXT);
+ private static final NetworkEvent recvNetworkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.RECV, 1).build();
+ private static final NetworkEvent sentNetworkEvent =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, 1).build();
+ private static final MessageEvent recvMessageEvent =
+ MessageEvent.builder(MessageEvent.Type.RECEIVED, 1).build();
+ private static final MessageEvent sentMessageEvent =
+ MessageEvent.builder(MessageEvent.Type.SENT, 1).build();
+ private static final Status status = Status.DEADLINE_EXCEEDED.withDescription("TooSlow");
+ private static final int CHILD_SPAN_COUNT = 13;
+ private final Random random = new Random(1234);
+ private final SpanContext spanContext =
+ SpanContext.create(
+ TraceId.generateRandomId(random), SpanId.generateRandomId(random), TraceOptions.DEFAULT);
+ private final SpanId parentSpanId = SpanId.generateRandomId(random);
+ private final Map<String, AttributeValue> attributesMap = new HashMap<String, AttributeValue>();
+ private final List<TimedEvent<Annotation>> annotationsList =
+ new ArrayList<TimedEvent<Annotation>>();
+ private final List<TimedEvent<NetworkEvent>> networkEventsList =
+ new ArrayList<SpanData.TimedEvent<NetworkEvent>>();
+ private final List<TimedEvent<MessageEvent>> messageEventsList =
+ new ArrayList<SpanData.TimedEvent<MessageEvent>>();
+ private final List<Link> linksList = new ArrayList<Link>();
+
+ private Attributes attributes;
+ private TimedEvents<Annotation> annotations;
+ private TimedEvents<NetworkEvent> networkEvents;
+ private TimedEvents<MessageEvent> messageEvents;
+ private Links links;
+
+ @Before
+ public void setUp() {
+ attributesMap.put("MyAttributeKey1", AttributeValue.longAttributeValue(10));
+ attributesMap.put("MyAttributeKey2", AttributeValue.booleanAttributeValue(true));
+ attributes = Attributes.create(attributesMap, 1);
+ annotationsList.add(SpanData.TimedEvent.create(eventTimestamp1, annotation));
+ annotationsList.add(SpanData.TimedEvent.create(eventTimestamp3, annotation));
+ annotations = TimedEvents.create(annotationsList, 2);
+ networkEventsList.add(SpanData.TimedEvent.create(eventTimestamp1, recvNetworkEvent));
+ networkEventsList.add(SpanData.TimedEvent.create(eventTimestamp2, sentNetworkEvent));
+ networkEvents = TimedEvents.create(networkEventsList, 3);
+ messageEventsList.add(SpanData.TimedEvent.create(eventTimestamp1, recvMessageEvent));
+ messageEventsList.add(SpanData.TimedEvent.create(eventTimestamp2, sentMessageEvent));
+ messageEvents = TimedEvents.create(messageEventsList, 3);
+ linksList.add(Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN));
+ links = Links.create(linksList, 0);
+ }
+
+ @Test
+ public void spanData_AllValues() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ true,
+ SPAN_NAME,
+ Kind.SERVER,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ assertThat(spanData.getContext()).isEqualTo(spanContext);
+ assertThat(spanData.getParentSpanId()).isEqualTo(parentSpanId);
+ assertThat(spanData.getHasRemoteParent()).isTrue();
+ assertThat(spanData.getName()).isEqualTo(SPAN_NAME);
+ assertThat(spanData.getKind()).isEqualTo(Kind.SERVER);
+ assertThat(spanData.getStartTimestamp()).isEqualTo(startTimestamp);
+ assertThat(spanData.getAttributes()).isEqualTo(attributes);
+ assertThat(spanData.getAnnotations()).isEqualTo(annotations);
+ assertThat(spanData.getNetworkEvents()).isEqualTo(networkEvents);
+ assertThat(spanData.getMessageEvents()).isEqualTo(messageEvents);
+ assertThat(spanData.getLinks()).isEqualTo(links);
+ assertThat(spanData.getChildSpanCount()).isEqualTo(CHILD_SPAN_COUNT);
+ assertThat(spanData.getStatus()).isEqualTo(status);
+ assertThat(spanData.getEndTimestamp()).isEqualTo(endTimestamp);
+ }
+
+ @Test
+ public void spanData_Create_Compatibility() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ true,
+ SPAN_NAME,
+ null,
+ startTimestamp,
+ attributes,
+ annotations,
+ networkEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ assertThat(spanData.getContext()).isEqualTo(spanContext);
+ assertThat(spanData.getParentSpanId()).isEqualTo(parentSpanId);
+ assertThat(spanData.getHasRemoteParent()).isTrue();
+ assertThat(spanData.getName()).isEqualTo(SPAN_NAME);
+ assertThat(spanData.getStartTimestamp()).isEqualTo(startTimestamp);
+ assertThat(spanData.getAttributes()).isEqualTo(attributes);
+ assertThat(spanData.getAnnotations()).isEqualTo(annotations);
+ assertThat(spanData.getNetworkEvents()).isEqualTo(networkEvents);
+ assertThat(spanData.getMessageEvents()).isEqualTo(messageEvents);
+ assertThat(spanData.getLinks()).isEqualTo(links);
+ assertThat(spanData.getChildSpanCount()).isEqualTo(CHILD_SPAN_COUNT);
+ assertThat(spanData.getStatus()).isEqualTo(status);
+ assertThat(spanData.getEndTimestamp()).isEqualTo(endTimestamp);
+ }
+
+ @Test
+ public void spanData_RootActiveSpan() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ null,
+ null,
+ SPAN_NAME,
+ null,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ null,
+ null,
+ null);
+ assertThat(spanData.getContext()).isEqualTo(spanContext);
+ assertThat(spanData.getParentSpanId()).isNull();
+ assertThat(spanData.getHasRemoteParent()).isNull();
+ assertThat(spanData.getName()).isEqualTo(SPAN_NAME);
+ assertThat(spanData.getStartTimestamp()).isEqualTo(startTimestamp);
+ assertThat(spanData.getAttributes()).isEqualTo(attributes);
+ assertThat(spanData.getAnnotations()).isEqualTo(annotations);
+ assertThat(spanData.getNetworkEvents()).isEqualTo(networkEvents);
+ assertThat(spanData.getMessageEvents()).isEqualTo(messageEvents);
+ assertThat(spanData.getLinks()).isEqualTo(links);
+ assertThat(spanData.getChildSpanCount()).isNull();
+ assertThat(spanData.getStatus()).isNull();
+ assertThat(spanData.getEndTimestamp()).isNull();
+ }
+
+ @Test
+ public void spanData_AllDataEmpty() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ false,
+ SPAN_NAME,
+ null,
+ startTimestamp,
+ Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0),
+ TimedEvents.create(Collections.<SpanData.TimedEvent<Annotation>>emptyList(), 0),
+ TimedEvents.create(Collections.<SpanData.TimedEvent<MessageEvent>>emptyList(), 0),
+ Links.create(Collections.<Link>emptyList(), 0),
+ 0,
+ status,
+ endTimestamp);
+ assertThat(spanData.getContext()).isEqualTo(spanContext);
+ assertThat(spanData.getParentSpanId()).isEqualTo(parentSpanId);
+ assertThat(spanData.getHasRemoteParent()).isFalse();
+ assertThat(spanData.getName()).isEqualTo(SPAN_NAME);
+ assertThat(spanData.getStartTimestamp()).isEqualTo(startTimestamp);
+ assertThat(spanData.getAttributes().getAttributeMap().isEmpty()).isTrue();
+ assertThat(spanData.getAnnotations().getEvents().isEmpty()).isTrue();
+ assertThat(spanData.getNetworkEvents().getEvents().isEmpty()).isTrue();
+ assertThat(spanData.getMessageEvents().getEvents().isEmpty()).isTrue();
+ assertThat(spanData.getLinks().getLinks().isEmpty()).isTrue();
+ assertThat(spanData.getChildSpanCount()).isEqualTo(0);
+ assertThat(spanData.getStatus()).isEqualTo(status);
+ assertThat(spanData.getEndTimestamp()).isEqualTo(endTimestamp);
+ }
+
+ @Test
+ public void spanDataEquals() {
+ SpanData allSpanData1 =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ false,
+ SPAN_NAME,
+ Kind.CLIENT,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ SpanData allSpanData2 =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ false,
+ SPAN_NAME,
+ Kind.CLIENT,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ SpanData emptySpanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ false,
+ SPAN_NAME,
+ null,
+ startTimestamp,
+ Attributes.create(Collections.<String, AttributeValue>emptyMap(), 0),
+ TimedEvents.create(Collections.<SpanData.TimedEvent<Annotation>>emptyList(), 0),
+ TimedEvents.create(Collections.<SpanData.TimedEvent<MessageEvent>>emptyList(), 0),
+ Links.create(Collections.<Link>emptyList(), 0),
+ 0,
+ status,
+ endTimestamp);
+ new EqualsTester()
+ .addEqualityGroup(allSpanData1, allSpanData2)
+ .addEqualityGroup(emptySpanData)
+ .testEquals();
+ }
+
+ @Test
+ public void spanData_ToString() {
+ String spanDataString =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ false,
+ SPAN_NAME,
+ Kind.CLIENT,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp)
+ .toString();
+ assertThat(spanDataString).contains(spanContext.toString());
+ assertThat(spanDataString).contains(parentSpanId.toString());
+ assertThat(spanDataString).contains(SPAN_NAME);
+ assertThat(spanDataString).contains(Kind.CLIENT.toString());
+ assertThat(spanDataString).contains(startTimestamp.toString());
+ assertThat(spanDataString).contains(attributes.toString());
+ assertThat(spanDataString).contains(annotations.toString());
+ assertThat(spanDataString).contains(messageEvents.toString());
+ assertThat(spanDataString).contains(links.toString());
+ assertThat(spanDataString).contains(status.toString());
+ assertThat(spanDataString).contains(endTimestamp.toString());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/internal/BaseMessageEventUtilsTest.java b/api/src/test/java/io/opencensus/trace/internal/BaseMessageEventUtilsTest.java
new file mode 100644
index 00000000..4f8c8508
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/internal/BaseMessageEventUtilsTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.trace.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.NetworkEvent;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BaseMessageEventUtils}. */
+@RunWith(JUnit4.class)
+public class BaseMessageEventUtilsTest {
+ private static final long SENT_EVENT_ID = 12345L;
+ private static final long RECV_EVENT_ID = 67890L;
+ private static final long UNCOMPRESSED_SIZE = 100;
+ private static final long COMPRESSED_SIZE = 99;
+
+ private static final MessageEvent SENT_MESSAGE_EVENT =
+ MessageEvent.builder(MessageEvent.Type.SENT, SENT_EVENT_ID)
+ .setUncompressedMessageSize(UNCOMPRESSED_SIZE)
+ .setCompressedMessageSize(COMPRESSED_SIZE)
+ .build();
+ private static final MessageEvent RECV_MESSAGE_EVENT =
+ MessageEvent.builder(MessageEvent.Type.RECEIVED, RECV_EVENT_ID)
+ .setUncompressedMessageSize(UNCOMPRESSED_SIZE)
+ .setCompressedMessageSize(COMPRESSED_SIZE)
+ .build();
+ private static final NetworkEvent SENT_NETWORK_EVENT =
+ NetworkEvent.builder(NetworkEvent.Type.SENT, SENT_EVENT_ID)
+ .setUncompressedMessageSize(UNCOMPRESSED_SIZE)
+ .setCompressedMessageSize(COMPRESSED_SIZE)
+ .build();
+ private static final NetworkEvent RECV_NETWORK_EVENT =
+ NetworkEvent.builder(NetworkEvent.Type.RECV, RECV_EVENT_ID)
+ .setUncompressedMessageSize(UNCOMPRESSED_SIZE)
+ .setCompressedMessageSize(COMPRESSED_SIZE)
+ .build();
+
+ @Test
+ public void networkEventToMessageEvent() {
+ assertThat(BaseMessageEventUtils.asMessageEvent(SENT_NETWORK_EVENT))
+ .isEqualTo(SENT_MESSAGE_EVENT);
+ assertThat(BaseMessageEventUtils.asMessageEvent(RECV_NETWORK_EVENT))
+ .isEqualTo(RECV_MESSAGE_EVENT);
+ }
+
+ @Test
+ public void messageEventToNetworkEvent() {
+ assertThat(BaseMessageEventUtils.asNetworkEvent(SENT_MESSAGE_EVENT))
+ .isEqualTo(SENT_NETWORK_EVENT);
+ assertThat(BaseMessageEventUtils.asNetworkEvent(RECV_MESSAGE_EVENT))
+ .isEqualTo(RECV_NETWORK_EVENT);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/propagation/BinaryFormatTest.java b/api/src/test/java/io/opencensus/trace/propagation/BinaryFormatTest.java
new file mode 100644
index 00000000..64544ffe
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/propagation/BinaryFormatTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.SpanContext;
+import java.text.ParseException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BinaryFormat}. */
+@RunWith(JUnit4.class)
+public class BinaryFormatTest {
+ private static final BinaryFormat binaryFormat = BinaryFormat.getNoopBinaryFormat();
+
+ @Test(expected = NullPointerException.class)
+ public void toBinaryValue_NullSpanContext() {
+ binaryFormat.toBinaryValue(null);
+ }
+
+ @Test
+ public void toBinaryValue_NotNullSpanContext() {
+ assertThat(binaryFormat.toBinaryValue(SpanContext.INVALID)).isEqualTo(new byte[0]);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void toByteArray_NullSpanContext() {
+ binaryFormat.toByteArray(null);
+ }
+
+ @Test
+ public void toByteArray_NotNullSpanContext() {
+ assertThat(binaryFormat.toByteArray(SpanContext.INVALID)).isEqualTo(new byte[0]);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromBinaryValue_NullInput() throws ParseException {
+ binaryFormat.fromBinaryValue(null);
+ }
+
+ @Test
+ public void fromBinaryValue_NotNullInput() throws ParseException {
+ assertThat(binaryFormat.fromBinaryValue(new byte[0])).isEqualTo(SpanContext.INVALID);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromByteArray_NullInput() throws SpanContextParseException {
+ binaryFormat.fromByteArray(null);
+ }
+
+ @Test
+ public void fromByteArray_NotNullInput() throws SpanContextParseException {
+ assertThat(binaryFormat.fromByteArray(new byte[0])).isEqualTo(SpanContext.INVALID);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/propagation/PropagationComponentTest.java b/api/src/test/java/io/opencensus/trace/propagation/PropagationComponentTest.java
new file mode 100644
index 00000000..ba64e98e
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/propagation/PropagationComponentTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link PropagationComponent}. */
+@RunWith(JUnit4.class)
+public class PropagationComponentTest {
+ private final PropagationComponent propagationComponent =
+ PropagationComponent.getNoopPropagationComponent();
+
+ @Test
+ public void implementationOfBinaryFormat() {
+ assertThat(propagationComponent.getBinaryFormat())
+ .isEqualTo(BinaryFormat.getNoopBinaryFormat());
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/propagation/SpanContextParseExceptionTest.java b/api/src/test/java/io/opencensus/trace/propagation/SpanContextParseExceptionTest.java
new file mode 100644
index 00000000..92efb35d
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/propagation/SpanContextParseExceptionTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SpanContextParseException}. */
+@RunWith(JUnit4.class)
+public class SpanContextParseExceptionTest {
+
+ @Test
+ public void createWithMessage() {
+ assertThat(new SpanContextParseException("my message").getMessage()).isEqualTo("my message");
+ }
+
+ @Test
+ public void createWithMessageAndCause() {
+ IOException cause = new IOException();
+ SpanContextParseException parseException = new SpanContextParseException("my message", cause);
+ assertThat(parseException.getMessage()).isEqualTo("my message");
+ assertThat(parseException.getCause()).isEqualTo(cause);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/propagation/TextFormatTest.java b/api/src/test/java/io/opencensus/trace/propagation/TextFormatTest.java
new file mode 100644
index 00000000..c2e6e127
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/propagation/TextFormatTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017, 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.trace.propagation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.propagation.TextFormat.Getter;
+import io.opencensus.trace.propagation.TextFormat.Setter;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TextFormat}. */
+@RunWith(JUnit4.class)
+public class TextFormatTest {
+ private static final TextFormat textFormat = TextFormat.getNoopTextFormat();
+
+ @Test(expected = NullPointerException.class)
+ public void inject_NullSpanContext() {
+ textFormat.inject(
+ null,
+ new Object(),
+ new Setter<Object>() {
+ @Override
+ public void put(Object carrier, String key, String value) {}
+ });
+ }
+
+ @Test
+ public void inject_NotNullSpanContext_DoesNotFail() {
+ textFormat.inject(
+ SpanContext.INVALID,
+ new Object(),
+ new Setter<Object>() {
+ @Override
+ public void put(Object carrier, String key, String value) {}
+ });
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromHeaders_NullGetter() throws SpanContextParseException {
+ textFormat.extract(new Object(), null);
+ }
+
+ @Test
+ public void fromHeaders_NotNullGetter() throws SpanContextParseException {
+ assertThat(
+ textFormat.extract(
+ new Object(),
+ new Getter<Object>() {
+ @Nullable
+ @Override
+ public String get(Object carrier, String key) {
+ return null;
+ }
+ }))
+ .isSameAs(SpanContext.INVALID);
+ }
+}
diff --git a/api/src/test/java/io/opencensus/trace/samplers/SamplersTest.java b/api/src/test/java/io/opencensus/trace/samplers/SamplersTest.java
new file mode 100644
index 00000000..7a46e97a
--- /dev/null
+++ b/api/src/test/java/io/opencensus/trace/samplers/SamplersTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2017, 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.trace.samplers;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.opencensus.trace.NoopSpan;
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Random;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Samplers}. */
+@RunWith(JUnit4.class)
+public class SamplersTest {
+ private static final String SPAN_NAME = "MySpanName";
+ private static final int NUM_SAMPLE_TRIES = 1000;
+ private final Random random = new Random(1234);
+ private final TraceId traceId = TraceId.generateRandomId(random);
+ private final SpanId parentSpanId = SpanId.generateRandomId(random);
+ private final SpanId spanId = SpanId.generateRandomId(random);
+ private final SpanContext sampledSpanContext =
+ SpanContext.create(traceId, parentSpanId, TraceOptions.builder().setIsSampled(true).build());
+ private final SpanContext notSampledSpanContext =
+ SpanContext.create(traceId, parentSpanId, TraceOptions.DEFAULT);
+ private final Span sampledSpan =
+ new NoopSpan(sampledSpanContext, EnumSet.of(Span.Options.RECORD_EVENTS));
+
+ @Test
+ public void alwaysSampleSampler_AlwaysReturnTrue() {
+ // Sampled parent.
+ assertThat(
+ Samplers.alwaysSample()
+ .shouldSample(
+ sampledSpanContext,
+ false,
+ traceId,
+ spanId,
+ "Another name",
+ Collections.<Span>emptyList()))
+ .isTrue();
+ // Not sampled parent.
+ assertThat(
+ Samplers.alwaysSample()
+ .shouldSample(
+ notSampledSpanContext,
+ false,
+ traceId,
+ spanId,
+ "Yet another name",
+ Collections.<Span>emptyList()))
+ .isTrue();
+ }
+
+ @Test
+ public void alwaysSampleSampler_ToString() {
+ assertThat(Samplers.alwaysSample().toString()).isEqualTo("AlwaysSampleSampler");
+ }
+
+ @Test
+ public void neverSampleSampler_AlwaysReturnFalse() {
+ // Sampled parent.
+ assertThat(
+ Samplers.neverSample()
+ .shouldSample(
+ sampledSpanContext,
+ false,
+ traceId,
+ spanId,
+ "bar",
+ Collections.<Span>emptyList()))
+ .isFalse();
+ // Not sampled parent.
+ assertThat(
+ Samplers.neverSample()
+ .shouldSample(
+ notSampledSpanContext,
+ false,
+ traceId,
+ spanId,
+ "quux",
+ Collections.<Span>emptyList()))
+ .isFalse();
+ }
+
+ @Test
+ public void neverSampleSampler_ToString() {
+ assertThat(Samplers.neverSample().toString()).isEqualTo("NeverSampleSampler");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void probabilitySampler_outOfRangeHighProbability() {
+ Samplers.probabilitySampler(1.01);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void probabilitySampler_outOfRangeLowProbability() {
+ Samplers.probabilitySampler(-0.00001);
+ }
+
+ // Applies the given sampler to NUM_SAMPLE_TRIES random traceId/spanId pairs.
+ private static void assertSamplerSamplesWithProbability(
+ Sampler sampler, SpanContext parent, List<Span> parentLinks, double probability) {
+ Random random = new Random(1234);
+ int count = 0; // Count of spans with sampling enabled
+ for (int i = 0; i < NUM_SAMPLE_TRIES; i++) {
+ if (sampler.shouldSample(
+ parent,
+ false,
+ TraceId.generateRandomId(random),
+ SpanId.generateRandomId(random),
+ SPAN_NAME,
+ parentLinks)) {
+ count++;
+ }
+ }
+ double proportionSampled = (double) count / NUM_SAMPLE_TRIES;
+ // Allow for a large amount of slop (+/- 10%) in number of sampled traces, to avoid flakiness.
+ assertThat(proportionSampled < probability + 0.1 && proportionSampled > probability - 0.1)
+ .isTrue();
+ }
+
+ @Test
+ public void probabilitySampler_DifferentProbabilities_NotSampledParent() {
+ final Sampler neverSample = Samplers.probabilitySampler(0.0);
+ assertSamplerSamplesWithProbability(
+ neverSample, notSampledSpanContext, Collections.<Span>emptyList(), 0.0);
+ final Sampler alwaysSample = Samplers.probabilitySampler(1.0);
+ assertSamplerSamplesWithProbability(
+ alwaysSample, notSampledSpanContext, Collections.<Span>emptyList(), 1.0);
+ final Sampler fiftyPercentSample = Samplers.probabilitySampler(0.5);
+ assertSamplerSamplesWithProbability(
+ fiftyPercentSample, notSampledSpanContext, Collections.<Span>emptyList(), 0.5);
+ final Sampler twentyPercentSample = Samplers.probabilitySampler(0.2);
+ assertSamplerSamplesWithProbability(
+ twentyPercentSample, notSampledSpanContext, Collections.<Span>emptyList(), 0.2);
+ final Sampler twoThirdsSample = Samplers.probabilitySampler(2.0 / 3.0);
+ assertSamplerSamplesWithProbability(
+ twoThirdsSample, notSampledSpanContext, Collections.<Span>emptyList(), 2.0 / 3.0);
+ }
+
+ @Test
+ public void probabilitySampler_DifferentProbabilities_SampledParent() {
+ final Sampler neverSample = Samplers.probabilitySampler(0.0);
+ assertSamplerSamplesWithProbability(
+ neverSample, sampledSpanContext, Collections.<Span>emptyList(), 1.0);
+ final Sampler alwaysSample = Samplers.probabilitySampler(1.0);
+ assertSamplerSamplesWithProbability(
+ alwaysSample, sampledSpanContext, Collections.<Span>emptyList(), 1.0);
+ final Sampler fiftyPercentSample = Samplers.probabilitySampler(0.5);
+ assertSamplerSamplesWithProbability(
+ fiftyPercentSample, sampledSpanContext, Collections.<Span>emptyList(), 1.0);
+ final Sampler twentyPercentSample = Samplers.probabilitySampler(0.2);
+ assertSamplerSamplesWithProbability(
+ twentyPercentSample, sampledSpanContext, Collections.<Span>emptyList(), 1.0);
+ final Sampler twoThirdsSample = Samplers.probabilitySampler(2.0 / 3.0);
+ assertSamplerSamplesWithProbability(
+ twoThirdsSample, sampledSpanContext, Collections.<Span>emptyList(), 1.0);
+ }
+
+ @Test
+ public void probabilitySampler_DifferentProbabilities_SampledParentLink() {
+ final Sampler neverSample = Samplers.probabilitySampler(0.0);
+ assertSamplerSamplesWithProbability(
+ neverSample, notSampledSpanContext, Arrays.asList(sampledSpan), 1.0);
+ final Sampler alwaysSample = Samplers.probabilitySampler(1.0);
+ assertSamplerSamplesWithProbability(
+ alwaysSample, notSampledSpanContext, Arrays.asList(sampledSpan), 1.0);
+ final Sampler fiftyPercentSample = Samplers.probabilitySampler(0.5);
+ assertSamplerSamplesWithProbability(
+ fiftyPercentSample, notSampledSpanContext, Arrays.asList(sampledSpan), 1.0);
+ final Sampler twentyPercentSample = Samplers.probabilitySampler(0.2);
+ assertSamplerSamplesWithProbability(
+ twentyPercentSample, notSampledSpanContext, Arrays.asList(sampledSpan), 1.0);
+ final Sampler twoThirdsSample = Samplers.probabilitySampler(2.0 / 3.0);
+ assertSamplerSamplesWithProbability(
+ twoThirdsSample, notSampledSpanContext, Arrays.asList(sampledSpan), 1.0);
+ }
+
+ @Test
+ public void probabilitySampler_SampleBasedOnTraceId() {
+ final Sampler defaultProbability = Samplers.probabilitySampler(0.0001);
+ // This traceId will not be sampled by the ProbabilitySampler because the first 8 bytes as long
+ // is not less than probability * Long.MAX_VALUE;
+ TraceId notSampledtraceId =
+ TraceId.fromBytes(
+ new byte[] {
+ (byte) 0x8F,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ });
+ assertThat(
+ defaultProbability.shouldSample(
+ null,
+ false,
+ notSampledtraceId,
+ SpanId.generateRandomId(random),
+ SPAN_NAME,
+ Collections.<Span>emptyList()))
+ .isFalse();
+ // This traceId will be sampled by the ProbabilitySampler because the first 8 bytes as long
+ // is less than probability * Long.MAX_VALUE;
+ TraceId sampledtraceId =
+ TraceId.fromBytes(
+ new byte[] {
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ (byte) 0xFF,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ });
+ assertThat(
+ defaultProbability.shouldSample(
+ null,
+ false,
+ sampledtraceId,
+ SpanId.generateRandomId(random),
+ SPAN_NAME,
+ Collections.<Span>emptyList()))
+ .isTrue();
+ }
+
+ @Test
+ public void probabilitySampler_getDescription() {
+ assertThat((Samplers.probabilitySampler(0.5)).getDescription())
+ .isEqualTo(String.format("ProbabilitySampler{%.6f}", 0.5));
+ }
+
+ @Test
+ public void probabilitySampler_ToString() {
+ assertThat((Samplers.probabilitySampler(0.5)).toString()).contains("0.5");
+ }
+}