aboutsummaryrefslogtreecommitdiff
path: root/api/src/main/java/io
diff options
context:
space:
mode:
authorsebright <sebright@google.com>2017-10-23 17:13:14 -0700
committerBogdan Drutu <bdrutu@google.com>2017-10-23 17:13:14 -0700
commit92e363fb2daa1a8aee308d3bd5fc20c9e83eeab2 (patch)
tree34cf4a254947a69b24ed522f997d15c072bfe8e4 /api/src/main/java/io
parente69bb99d61c529fc9ad8c0be83d1f438c93a63af (diff)
downloadopencensus-java-92e363fb2daa1a8aee308d3bd5fc20c9e83eeab2.tar.gz
Move stats and tags packages to opencensus-api to prepare for release. (#723)
Diffstat (limited to 'api/src/main/java/io')
-rw-r--r--api/src/main/java/io/opencensus/stats/Aggregation.java179
-rw-r--r--api/src/main/java/io/opencensus/stats/AggregationData.java312
-rw-r--r--api/src/main/java/io/opencensus/stats/BucketBoundaries.java58
-rw-r--r--api/src/main/java/io/opencensus/stats/Measure.java150
-rw-r--r--api/src/main/java/io/opencensus/stats/MeasureMap.java128
-rw-r--r--api/src/main/java/io/opencensus/stats/Measurement.java99
-rw-r--r--api/src/main/java/io/opencensus/stats/NoopStats.java151
-rw-r--r--api/src/main/java/io/opencensus/stats/Stats.java95
-rw-r--r--api/src/main/java/io/opencensus/stats/StatsCollectionState.java36
-rw-r--r--api/src/main/java/io/opencensus/stats/StatsComponent.java50
-rw-r--r--api/src/main/java/io/opencensus/stats/StatsRecorder.java42
-rw-r--r--api/src/main/java/io/opencensus/stats/View.java229
-rw-r--r--api/src/main/java/io/opencensus/stats/ViewData.java182
-rw-r--r--api/src/main/java/io/opencensus/stats/ViewManager.java34
-rw-r--r--api/src/main/java/io/opencensus/tags/InternalUtils.java31
-rw-r--r--api/src/main/java/io/opencensus/tags/NoopTags.java232
-rw-r--r--api/src/main/java/io/opencensus/tags/Tag.java208
-rw-r--r--api/src/main/java/io/opencensus/tags/TagContext.java99
-rw-r--r--api/src/main/java/io/opencensus/tags/TagContextBuilder.java83
-rw-r--r--api/src/main/java/io/opencensus/tags/TagKey.java244
-rw-r--r--api/src/main/java/io/opencensus/tags/TagValue.java182
-rw-r--r--api/src/main/java/io/opencensus/tags/Tagger.java78
-rw-r--r--api/src/main/java/io/opencensus/tags/TaggingState.java40
-rw-r--r--api/src/main/java/io/opencensus/tags/Tags.java104
-rw-r--r--api/src/main/java/io/opencensus/tags/TagsComponent.java52
-rw-r--r--api/src/main/java/io/opencensus/tags/package-info.java33
-rw-r--r--api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java51
-rw-r--r--api/src/main/java/io/opencensus/tags/propagation/TagContextParseException.java43
-rw-r--r--api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java31
-rw-r--r--api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java52
30 files changed, 3308 insertions, 0 deletions
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..f12517c8
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Aggregation.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.stats;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+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>Mean
+ * <li>Distribution
+ * </ul>
+ *
+ * <p>When creating a {@link View}, one {@link Aggregation} needs to be specified as how to
+ * aggregate {@code MeasureValue}s.
+ */
+@Immutable
+public abstract class Aggregation {
+
+ private Aggregation() {
+ }
+
+ /**
+ * Applies the given match function to the underlying data type.
+ */
+ public abstract <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Mean, T> p2,
+ Function<? super Distribution, T> p3,
+ Function<? super Aggregation, T> defaultFunction);
+
+ /** Calculate sum on aggregated {@code MeasureValue}s. */
+ @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}.
+ */
+ public static Sum create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Mean, T> p2,
+ Function<? super Distribution, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return p0.apply(this);
+ }
+ }
+
+ /** Calculate count on aggregated {@code MeasureValue}s. */
+ @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}.
+ */
+ public static Count create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Mean, T> p2,
+ Function<? super Distribution, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return p1.apply(this);
+ }
+ }
+
+ /** Calculate mean on aggregated {@code MeasureValue}s. */
+ @Immutable
+ @AutoValue
+ 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}.
+ */
+ public static Mean create() {
+ return INSTANCE;
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Mean, T> p2,
+ Function<? super Distribution, T> p3,
+ Function<? super Aggregation, T> defaultFunction) {
+ return p2.apply(this);
+ }
+ }
+
+ /**
+ * Calculate distribution stats on aggregated {@code MeasureValue}s. Distribution includes
+ * mean, count, histogram, min, max and sum of squared deviations.
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class Distribution extends Aggregation {
+
+ Distribution() {
+ }
+
+ /**
+ * Construct a {@code Distribution}.
+ *
+ * @return a new {@code Distribution}.
+ */
+ public static Distribution create(BucketBoundaries bucketBoundaries) {
+ checkNotNull(bucketBoundaries, "bucketBoundaries should not be null.");
+ return new AutoValue_Aggregation_Distribution(bucketBoundaries);
+ }
+
+ public abstract BucketBoundaries getBucketBoundaries();
+
+ @Override
+ public final <T> T match(
+ Function<? super Sum, T> p0,
+ Function<? super Count, T> p1,
+ Function<? super Mean, T> p2,
+ Function<? super Distribution, 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..fdf09592
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/AggregationData.java
@@ -0,0 +1,312 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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 5 types of basic aggregation values:
+ * <ul>
+ * <li>SumDataDouble
+ * <li>SumDataLong
+ * <li>CountData
+ * <li>MeanData
+ * <li>DistributionData
+ * </ul>
+ *
+ * <p>{@link ViewData} will contain one {@link AggregationData}, corresponding to its
+ * {@link Aggregation} definition in {@link View}.
+ */
+@Immutable
+public abstract class AggregationData {
+
+ private AggregationData() {
+ }
+
+ /**
+ * Applies the given match function to the underlying data type.
+ */
+ public abstract <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super MeanData, T> p3,
+ Function<? super DistributionData, T> p4,
+ Function<? super AggregationData, T> defaultFunction);
+
+ /** The sum value of aggregated {@code MeasureValueDouble}s. */
+ @Immutable
+ @AutoValue
+ public abstract static class SumDataDouble extends AggregationData {
+
+ SumDataDouble() {
+ }
+
+ /**
+ * Creates a {@code SumDataDouble}.
+ *
+ * @param sum the aggregated sum.
+ * @return a {@code SumDataDouble}.
+ */
+ public static SumDataDouble create(double sum) {
+ return new AutoValue_AggregationData_SumDataDouble(sum);
+ }
+
+ /**
+ * Returns the aggregated sum.
+ *
+ * @return the aggregated sum.
+ */
+ 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 MeanData, T> p3,
+ Function<? super DistributionData, T> p4,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p0.apply(this);
+ }
+ }
+
+ /** The sum value of aggregated {@code MeasureValueLong}s. */
+ @Immutable
+ @AutoValue
+ public abstract static class SumDataLong extends AggregationData {
+
+ SumDataLong() {
+ }
+
+ /**
+ * Creates a {@code SumDataLong}.
+ *
+ * @param sum the aggregated sum.
+ * @return a {@code SumDataLong}.
+ */
+ public static SumDataLong create(long sum) {
+ return new AutoValue_AggregationData_SumDataLong(sum);
+ }
+
+ /**
+ * Returns the aggregated sum.
+ *
+ * @return the aggregated sum.
+ */
+ 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 MeanData, T> p3,
+ Function<? super DistributionData, T> p4,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p1.apply(this);
+ }
+ }
+
+ /** The count value of aggregated {@code MeasureValue}s. */
+ @Immutable
+ @AutoValue
+ public abstract static class CountData extends AggregationData {
+
+ CountData() {
+ }
+
+ /**
+ * Creates a {@code CountData}.
+ *
+ * @param count the aggregated count.
+ * @return a {@code CountData}.
+ */
+ public static CountData create(long count) {
+ return new AutoValue_AggregationData_CountData(count);
+ }
+
+ /**
+ * Returns the aggregated count.
+ *
+ * @return the aggregated count.
+ */
+ 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 MeanData, T> p3,
+ Function<? super DistributionData, T> p4,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p2.apply(this);
+ }
+ }
+
+ /** The mean value of aggregated {@code MeasureValue}s. */
+ @Immutable
+ @AutoValue
+ 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}.
+ */
+ public static MeanData create(double mean, long count) {
+ return new AutoValue_AggregationData_MeanData(mean, count);
+ }
+
+ /**
+ * Returns the aggregated mean.
+ *
+ * @return the aggregated mean.
+ */
+ public abstract double getMean();
+
+ /**
+ * Returns the aggregated count.
+ *
+ * @return the aggregated count.
+ */
+ 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 MeanData, T> p3,
+ Function<? super DistributionData, T> p4,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p3.apply(this);
+ }
+ }
+
+ /**
+ * The distribution stats of aggregated {@code MeasureValue}s. Distribution stats include mean,
+ * count, histogram, min, max and sum of squared deviations.
+ */
+ @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.
+ * @return a {@code DistributionData}.
+ */
+ public static DistributionData create(
+ double mean, long count, double min, double max, double sumOfSquaredDeviations,
+ long[] bucketCounts) {
+ if (min != Double.POSITIVE_INFINITY || max != Double.NEGATIVE_INFINITY) {
+ checkArgument(min <= max, "max should be greater or equal to min.");
+ }
+
+ checkNotNull(bucketCounts, "bucket counts should not be null.");
+ List<Long> boxedBucketCounts = new ArrayList<Long>();
+ for (long bucketCount : bucketCounts) {
+ boxedBucketCounts.add(bucketCount);
+ }
+
+ return new AutoValue_AggregationData_DistributionData(
+ mean, count, min, max, sumOfSquaredDeviations,
+ Collections.unmodifiableList(boxedBucketCounts));
+ }
+
+ /**
+ * Returns the aggregated mean.
+ *
+ * @return the aggregated mean.
+ */
+ public abstract double getMean();
+
+ /**
+ * Returns the aggregated count.
+ *
+ * @return the aggregated count.
+ */
+ public abstract long getCount();
+
+ /**
+ * Returns the minimum of the population values.
+ *
+ * @return the minimum of the population values.
+ */
+ public abstract double getMin();
+
+ /**
+ * Returns the maximum of the population values.
+ *
+ * @return the maximum of the population values.
+ */
+ public abstract double getMax();
+
+ /**
+ * Returns the aggregated sum of squared deviations.
+ *
+ * @return the aggregated sum of squared deviations.
+ */
+ 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.
+ */
+ public abstract List<Long> getBucketCounts();
+
+ @Override
+ public final <T> T match(
+ Function<? super SumDataDouble, T> p0,
+ Function<? super SumDataLong, T> p1,
+ Function<? super CountData, T> p2,
+ Function<? super MeanData, T> p3,
+ Function<? super DistributionData, T> p4,
+ Function<? super AggregationData, T> defaultFunction) {
+ return p4.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..b9a808f4
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/BucketBoundaries.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.stats;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auto.value.AutoValue;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.concurrent.Immutable;
+
+/** The bucket boundaries for a histogram. */
+@Immutable
+@AutoValue
+public abstract class BucketBoundaries {
+
+ /**
+ * @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.
+ */
+ public static final BucketBoundaries create(List<Double> bucketBoundaries) {
+ checkNotNull(bucketBoundaries, "bucketBoundaries list should not be null.");
+ 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);
+ checkArgument(lower < next, "Bucket boundaries not sorted.");
+ lower = next;
+ }
+ }
+ return new AutoValue_BucketBoundaries(Collections.unmodifiableList(bucketBoundariesCopy));
+ }
+
+ /**
+ * @return a list of histogram bucket boundaries.
+ */
+ 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..e7a0ec96
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Measure.java
@@ -0,0 +1,150 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.common.Function;
+import io.opencensus.internal.StringUtil;
+import javax.annotation.concurrent.Immutable;
+
+/** The definition of the {@link Measurement} that is taken by OpenCensus library. */
+@Immutable
+public abstract class Measure {
+
+ @VisibleForTesting static final int NAME_MAX_LENGTH = 256;
+
+ /**
+ * Applies the given match function to the underlying data type.
+ */
+ 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
+ * 256 characters.
+ *
+ * <p>Suggested format for name: {@code <web_host>/<path>}.
+ */
+ public abstract String getName();
+
+ /**
+ * Detailed description of the measure, used in documentation.
+ */
+ public abstract String getDescription();
+
+ /**
+ * The units in which {@link Measure} values are measured.
+ *
+ * <p>The suggested grammar for a unit is as follows:
+ * Expression = Component { "." Component } { "/" Component } ;
+ * Component = [ PREFIX ] UNIT [ Annotation ] | Annotation | "1" ;
+ * Annotation = "{" NAME "}" ;
+ * For example, string “MBy{transmitted}/ms” stands for megabytes per milliseconds, and the
+ * annotation transmitted inside {} is just a comment of the unit.
+ */
+ // 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. */
+ @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}.
+ */
+ public static MeasureDouble create(String name, String description, String unit) {
+ checkArgument(StringUtil.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH,
+ "Name should be a ASCII string with a length no greater than 256 characters.");
+ 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. */
+ @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}.
+ */
+ public static MeasureLong create(String name, String description, String unit) {
+ checkArgument(StringUtil.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH,
+ "Name should be a ASCII string with a length no greater than 256 characters.");
+ 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..0cc972f0
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/MeasureMap.java
@@ -0,0 +1,128 @@
+/*
+ * 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.stats.Measure.MeasureDouble;
+import io.opencensus.stats.Measure.MeasureLong;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A map from {@link Measure}'s to measured values.
+ */
+public final class MeasureMap {
+
+ /**
+ * Returns a {@link Builder} for the {@link MeasureMap} class.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns an {@link Iterator} over the measure/value mappings in this {@link MeasureMap}.
+ * The {@code Iterator} does not support {@link Iterator#remove()}.
+ */
+ public Iterator<Measurement> iterator() {
+ return new MeasureMapIterator();
+ }
+
+ private final ArrayList<Measurement> measurements;
+
+ private MeasureMap(ArrayList<Measurement> measurements) {
+ this.measurements = measurements;
+ }
+
+ /**
+ * Builder for the {@link MeasureMap} class.
+ */
+ public static class Builder {
+ /**
+ * 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
+ */
+ public Builder put(MeasureDouble measure, double value) {
+ measurements.add(Measurement.MeasurementDouble.create(measure, value));
+ return this;
+ }
+
+ /**
+ * 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
+ */
+ public Builder put(MeasureLong measure, long value) {
+ measurements.add(Measurement.MeasurementLong.create(measure, value));
+ return this;
+ }
+
+ /**
+ * Constructs a {@link MeasureMap} from the current measurements.
+ */
+ public MeasureMap build() {
+ // Note: this makes adding measurements quadratic but is fastest for the sizes of
+ // MeasureMaps that we should see. We may want to go to a strategy of sort/eliminate
+ // for larger MeasureMaps.
+ for (int i = measurements.size() - 1; i >= 0; i--) {
+ for (int j = i - 1; j >= 0; j--) {
+ if (measurements.get(i).getMeasure() == measurements.get(j).getMeasure()) {
+ measurements.remove(j);
+ j--;
+ }
+ }
+ }
+ return new MeasureMap(measurements);
+ }
+
+ private final ArrayList<Measurement> measurements = new ArrayList<Measurement>();
+
+ private Builder() {
+ }
+ }
+
+ // Provides an unmodifiable Iterator over this instance's measurements.
+ private final class MeasureMapIterator implements Iterator<Measurement> {
+ @Override
+ public boolean hasNext() {
+ return position < length;
+ }
+
+ @Override
+ public Measurement next() {
+ if (position >= measurements.size()) {
+ throw new NoSuchElementException();
+ }
+ return measurements.get(position++);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private final int length = measurements.size();
+ private int position = 0;
+ }
+}
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..10fc5888
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Measurement.java
@@ -0,0 +1,99 @@
+/*
+ * 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. */
+@Immutable
+public abstract class Measurement {
+
+ /**
+ * Applies the given match function to the underlying data type.
+ */
+ 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}.
+ */
+ public abstract Measure getMeasure();
+
+ // Prevents this class from being subclassed anywhere else.
+ private Measurement() {
+ }
+
+ /** {@code Double} typed {@link Measurement}. */
+ @Immutable
+ @AutoValue
+ public abstract static class MeasurementDouble extends Measurement {
+ MeasurementDouble() {}
+
+ /**
+ * Constructs a new {@link MeasurementDouble}.
+ */
+ public static MeasurementDouble create(MeasureDouble measure, double value) {
+ return new AutoValue_Measurement_MeasurementDouble(measure, value);
+ }
+
+ @Override
+ public abstract MeasureDouble getMeasure();
+
+ 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}. */
+ @Immutable
+ @AutoValue
+ public abstract static class MeasurementLong extends Measurement {
+ MeasurementLong() {}
+
+ /**
+ * Constructs a new {@link MeasurementLong}.
+ */
+ public static MeasurementLong create(MeasureLong measure, long value) {
+ return new AutoValue_Measurement_MeasurementLong(measure, value);
+ }
+
+ @Override
+ public abstract MeasureLong getMeasure();
+
+ 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..79ddb359
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/NoopStats.java
@@ -0,0 +1,151 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.stats.ViewData.AggregationWindowData;
+import io.opencensus.stats.ViewData.AggregationWindowData.CumulativeData;
+import io.opencensus.stats.ViewData.AggregationWindowData.IntervalData;
+import io.opencensus.tags.TagContext;
+import io.opencensus.tags.TagValue;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** 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 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();
+
+ @Override
+ public ViewManager getViewManager() {
+ return viewManager;
+ }
+
+ @Override
+ public StatsRecorder getStatsRecorder() {
+ return getNoopStatsRecorder();
+ }
+
+ @Override
+ public StatsCollectionState getState() {
+ return StatsCollectionState.DISABLED;
+ }
+
+ @Override
+ public void setState(StatsCollectionState state) {
+ Preconditions.checkNotNull(state, "state");
+ }
+ }
+
+ @Immutable
+ private static final class NoopStatsRecorder extends StatsRecorder {
+ static final StatsRecorder INSTANCE = new NoopStatsRecorder();
+
+ @Override
+ public void record(TagContext tags, MeasureMap measureValues) {
+ checkNotNull(tags, "tags");
+ checkNotNull(measureValues, "measureValues");
+ }
+ }
+
+ @ThreadSafe
+ private static final class NoopViewManager extends ViewManager {
+ private static final Timestamp ZERO_TIMESTAMP = Timestamp.create(0, 0);
+
+ @GuardedBy("views")
+ private final Map<View.Name, View> views = Maps.newHashMap();
+
+ @Override
+ public void registerView(View newView) {
+ checkNotNull(newView, "newView");
+ synchronized (views) {
+ View existing = views.get(newView.getName());
+ checkArgument(
+ existing == null || newView.equals(existing),
+ "A different view with the same name already exists.");
+ if (existing == null) {
+ views.put(newView.getName(), newView);
+ }
+ }
+ }
+
+ @Override
+ public ViewData getView(View.Name name) {
+ checkNotNull(name, "name");
+ synchronized (views) {
+ View view = views.get(name);
+ if (view == null) {
+ throw new IllegalArgumentException("View is not registered.");
+ } else {
+ return ViewData.create(
+ view,
+ Collections.<List<TagValue>, AggregationData>emptyMap(),
+ view.getWindow()
+ .match(
+ Functions.<AggregationWindowData>returnConstant(
+ CumulativeData.create(ZERO_TIMESTAMP, ZERO_TIMESTAMP)),
+ Functions.<AggregationWindowData>returnConstant(
+ IntervalData.create(ZERO_TIMESTAMP)),
+ Functions.<AggregationWindowData>throwAssertionError()));
+ }
+ }
+ }
+ }
+}
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..016455e9
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/Stats.java
@@ -0,0 +1,95 @@
+/*
+ * 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.common.annotations.VisibleForTesting;
+import io.opencensus.internal.Provider;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/** Class for accessing the default {@link StatsComponent}. */
+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}. */
+ public static StatsRecorder getStatsRecorder() {
+ return statsComponent.getStatsRecorder();
+ }
+
+ /** Returns the default {@link ViewManager}. */
+ 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}.
+ *
+ * @return the current {@code StatsCollectionState}.
+ */
+ public static StatsCollectionState getState() {
+ return statsComponent.getState();
+ }
+
+ /**
+ * Sets the current {@code StatsCollectionState}.
+ *
+ * <p>When no implementation is available, {@code setState} has no effect.
+ *
+ * @param state the new {@code StatsCollectionState}.
+ */
+ public static void setState(StatsCollectionState state) {
+ statsComponent.setState(state);
+ }
+
+ // Any provider that may be used for StatsComponent can be added here.
+ @VisibleForTesting
+ static StatsComponent loadStatsComponent(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", 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", 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..7e5641b1
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/StatsCollectionState.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.stats;
+
+/** State of the {@link StatsComponent}. */
+public enum StatsCollectionState {
+
+ /**
+ * State that fully enables stats collection.
+ *
+ * <p>The {@link StatsComponent} collects stats for registered views.
+ */
+ 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)}.
+ */
+ 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..69fbcb5c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/StatsComponent.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+public abstract class StatsComponent {
+
+ /** Returns the default {@link ViewManager}. */
+ public abstract ViewManager getViewManager();
+
+ /** Returns the default {@link StatsRecorder}. */
+ public abstract StatsRecorder getStatsRecorder();
+
+ /**
+ * Returns the current {@code StatsCollectionState}.
+ *
+ * <p>When no implementation is available, {@code getState} always returns {@link
+ * StatsCollectionState#DISABLED}.
+ *
+ * @return the current {@code StatsCollectionState}.
+ */
+ public abstract StatsCollectionState getState();
+
+ /**
+ * Sets the current {@code StatsCollectionState}.
+ *
+ * <p>When no implementation is available, {@code setState} has no effect.
+ *
+ * @param state the new {@code StatsCollectionState}.
+ */
+ 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..cab21c29
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/StatsRecorder.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.stats;
+
+import io.opencensus.tags.TagContext;
+import io.opencensus.tags.unsafe.ContextUtils;
+
+/** Provides methods to record stats against tags. */
+public abstract class StatsRecorder {
+
+ /**
+ * Records a set of measurements with the tags in the current context.
+ *
+ * @param measureValues the measurements to record.
+ */
+ public final void record(MeasureMap measureValues) {
+ // Use the context key directly, to avoid depending on the tags implementation.
+ record(ContextUtils.TAG_CONTEXT_KEY.get(), measureValues);
+ }
+
+ /**
+ * Records a set of measurements with a set of tags.
+ *
+ * @param tags the tags associated with the measurements.
+ * @param measureValues the measurements to record.
+ */
+ public abstract void record(TagContext tags, MeasureMap measureValues);
+}
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..31b06461
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/View.java
@@ -0,0 +1,229 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.common.Duration;
+import io.opencensus.common.Function;
+import io.opencensus.internal.StringUtil;
+import io.opencensus.tags.TagKey;
+import java.util.ArrayList;
+import java.util.Collections;
+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.
+ */
+@Immutable
+@AutoValue
+public abstract class View {
+
+ @VisibleForTesting static final int NAME_MAX_LENGTH = 256;
+
+ View() {
+ }
+
+ /**
+ * Name of view. Must be unique.
+ */
+ public abstract Name getName();
+
+ /**
+ * More detailed description, for documentation purposes.
+ */
+ public abstract String getDescription();
+
+ /**
+ * Measure type of this view.
+ */
+ public abstract Measure getMeasure();
+
+ /**
+ * The {@link Aggregation} associated with this {@link View}.
+ */
+ 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.
+ */
+ public abstract List<TagKey> getColumns();
+
+ /**
+ * Returns the time {@link AggregationWindow} for this {@code View}.
+ *
+ * @return the time {@link AggregationWindow}.
+ */
+ 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}.
+ */
+ public static View create(
+ Name name,
+ String description,
+ Measure measure,
+ Aggregation aggregation,
+ List<? extends TagKey> columns,
+ AggregationWindow window) {
+ checkArgument(new HashSet<TagKey>(columns).size() == columns.size(),
+ "Columns have duplicate.");
+
+ return new AutoValue_View(
+ name,
+ description,
+ measure,
+ aggregation,
+ Collections.unmodifiableList(new ArrayList<TagKey>(columns)),
+ window);
+ }
+
+ /**
+ * The name of a {@code View}.
+ */
+ // 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}.
+ */
+ public abstract String asString();
+
+ /**
+ * Creates a {@code View.Name} from a {@code String}. Should be a ASCII string with a length
+ * no greater than 256 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}.
+ */
+ public static Name create(String name) {
+ checkArgument(StringUtil.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH,
+ "Name should be a ASCII string with a length no greater than 256 characters.");
+ return new AutoValue_View_Name(name);
+ }
+ }
+
+ /** The time window for a {@code View}. */
+ @Immutable
+ public abstract static class AggregationWindow {
+
+ private AggregationWindow() {}
+
+ /**
+ * Applies the given match function to the underlying data type.
+ */
+ 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}. */
+ @Immutable
+ @AutoValue
+ 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}.
+ */
+ 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.} */
+ @Immutable
+ @AutoValue
+ 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}.
+ */
+ 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}.
+ */
+ public static Interval create(Duration duration) {
+ 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..e4872091
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/ViewData.java
@@ -0,0 +1,182 @@
+/*
+ * 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 com.google.common.collect.Maps;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.Timestamp;
+import io.opencensus.tags.TagValue;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.annotation.concurrent.Immutable;
+
+/** The aggregated data for a particular {@link View}. */
+@Immutable
+@AutoValue
+public abstract class ViewData {
+
+ // Prevents this class from being subclassed anywhere else.
+ ViewData() {}
+
+ /** The {@link View} associated with this {@link ViewData}. */
+ public abstract View getView();
+
+ /**
+ * The {@link AggregationData} grouped by combination of tag values, associated with this
+ * {@link ViewData}.
+ */
+ public abstract Map<List<TagValue>, AggregationData> getAggregationMap();
+
+ /**
+ * Returns the {@link AggregationWindowData} associated with this {@link ViewData}.
+ *
+ * @return the {@code AggregationWindowData}.
+ */
+ public abstract AggregationWindowData getWindowData();
+
+ /** Constructs a new {@link ViewData}. */
+ public static ViewData create(
+ View view,
+ Map<? extends List<? extends TagValue>, ? extends AggregationData> map,
+ final AggregationWindowData windowData) {
+ view.getWindow()
+ .match(
+ new Function<View.AggregationWindow.Cumulative, Void>() {
+ @Override
+ public Void apply(View.AggregationWindow.Cumulative arg) {
+ if (!(windowData instanceof AggregationWindowData.CumulativeData)) {
+ throw new IllegalArgumentException(
+ "AggregationWindow and AggregationWindowData types mismatch. "
+ + "AggregationWindow: "
+ + arg
+ + " AggregationWindowData: "
+ + windowData);
+ }
+ return null;
+ }
+ },
+ new Function<View.AggregationWindow.Interval, Void>() {
+ @Override
+ public Void apply(View.AggregationWindow.Interval arg) {
+ if (!(windowData instanceof AggregationWindowData.IntervalData)) {
+ throw new IllegalArgumentException(
+ "AggregationWindow and AggregationWindowData types mismatch. "
+ + "AggregationWindow: "
+ + arg
+ + " AggregationWindowData: "
+ + windowData);
+ }
+ return null;
+ }
+ },
+ Functions.<Void>throwIllegalArgumentException());
+
+ Map<List<TagValue>, AggregationData> deepCopy = Maps.newHashMap();
+ for (Entry<? extends List<? extends TagValue>, ? extends AggregationData> entry : map
+ .entrySet()) {
+ deepCopy.put(
+ Collections.unmodifiableList(new ArrayList<TagValue>(entry.getKey())),
+ entry.getValue());
+ }
+
+ return new AutoValue_ViewData(view, Collections.unmodifiableMap(deepCopy), windowData);
+ }
+
+ /** The {@code AggregationWindowData} for a {@link ViewData}. */
+ @Immutable
+ public abstract static class AggregationWindowData {
+
+ private AggregationWindowData() {}
+
+ /** Applies the given match function to the underlying data type. */
+ public abstract <T> T match(
+ Function<? super CumulativeData, T> p0,
+ Function<? super IntervalData, T> p1,
+ Function<? super AggregationWindowData, T> defaultFunction);
+
+ /** Cumulative {@code AggregationWindowData.} */
+ @Immutable
+ @AutoValue
+ public abstract static class CumulativeData extends AggregationWindowData {
+
+ CumulativeData() {}
+
+ /**
+ * Returns the start {@code Timestamp} for a {@link CumulativeData}.
+ *
+ * @return the start {@code Timestamp}.
+ */
+ public abstract Timestamp getStart();
+
+ /**
+ * Returns the end {@code Timestamp} for a {@link CumulativeData}.
+ *
+ * @return the end {@code Timestamp}.
+ */
+ 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}. */
+ 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.} */
+ @Immutable
+ @AutoValue
+ public abstract static class IntervalData extends AggregationWindowData {
+
+ IntervalData() {}
+
+ /**
+ * Returns the end {@code Timestamp} for an {@link IntervalData}.
+ *
+ * @return the end {@code Timestamp}.
+ */
+ 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}. */
+ 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..16bf749e
--- /dev/null
+++ b/api/src/main/java/io/opencensus/stats/ViewManager.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Provides facilities to register {@link View}s for collecting stats and retrieving
+ * stats data as a {@link ViewData}.
+ */
+public abstract class ViewManager {
+ /**
+ * Pull model for stats. Registers a {@link View} that will collect data to be accessed
+ * via {@link #getView(View.Name)}.
+ */
+ public abstract void registerView(View view);
+
+ /**
+ * Returns the current stats data, {@link ViewData}, associated with the given view name.
+ */
+ public abstract ViewData getView(View.Name view);
+}
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..c55ca7d4
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/InternalUtils.java
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+@io.opencensus.common.Internal
+public final class InternalUtils {
+ private InternalUtils() {}
+
+ 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..2267edb0
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/NoopTags.java
@@ -0,0 +1,232 @@
+/*
+ * 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.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Preconditions;
+import io.opencensus.common.Scope;
+import io.opencensus.internal.NoopScope;
+import io.opencensus.tags.TagKey.TagKeyBoolean;
+import io.opencensus.tags.TagKey.TagKeyLong;
+import io.opencensus.tags.TagKey.TagKeyString;
+import io.opencensus.tags.TagValue.TagValueBoolean;
+import io.opencensus.tags.TagValue.TagValueLong;
+import io.opencensus.tags.TagValue.TagValueString;
+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;
+
+/** 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 getNoopTagsComponent() {
+ return NoopTagsComponent.INSTANCE;
+ }
+
+ /**
+ * 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;
+ }
+
+ @Immutable
+ private static final class NoopTagsComponent extends TagsComponent {
+ static final TagsComponent INSTANCE = new NoopTagsComponent();
+
+ @Override
+ public Tagger getTagger() {
+ return getNoopTagger();
+ }
+
+ @Override
+ public TagPropagationComponent getTagPropagationComponent() {
+ return getNoopTagPropagationComponent();
+ }
+
+ @Override
+ public TaggingState getState() {
+ return TaggingState.DISABLED;
+ }
+
+ @Override
+ public void setState(TaggingState state) {
+ Preconditions.checkNotNull(state, "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) {
+ checkNotNull(tags, "tags");
+ return getNoopTagContextBuilder();
+ }
+
+ @Override
+ public TagContextBuilder currentBuilder() {
+ return getNoopTagContextBuilder();
+ }
+
+ @Override
+ public Scope withTagContext(TagContext tags) {
+ checkNotNull(tags, "tags");
+ return NoopScope.getInstance();
+ }
+ }
+
+ @Immutable
+ private static final class NoopTagContextBuilder extends TagContextBuilder {
+ static final TagContextBuilder INSTANCE = new NoopTagContextBuilder();
+
+ @Override
+ public TagContextBuilder put(TagKeyString key, TagValueString value) {
+ checkNotNull(key, "key");
+ checkNotNull(value, "value");
+ return this;
+ }
+
+ @Override
+ public TagContextBuilder put(TagKeyLong key, TagValueLong value) {
+ checkNotNull(key, "key");
+ checkNotNull(value, "value");
+ return this;
+ }
+
+ @Override
+ public TagContextBuilder put(TagKeyBoolean key, TagValueBoolean value) {
+ checkNotNull(key, "key");
+ checkNotNull(value, "value");
+ return this;
+ }
+
+ @Override
+ public TagContextBuilder remove(TagKey key) {
+ 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) {
+ checkNotNull(tags, "tags");
+ return EMPTY_BYTE_ARRAY;
+ }
+
+ @Override
+ public TagContext fromByteArray(byte[] bytes) {
+ 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..a53cd838
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/Tag.java
@@ -0,0 +1,208 @@
+/*
+ * 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.common.Function;
+import io.opencensus.tags.TagKey.TagKeyBoolean;
+import io.opencensus.tags.TagKey.TagKeyLong;
+import io.opencensus.tags.TagKey.TagKeyString;
+import io.opencensus.tags.TagValue.TagValueBoolean;
+import io.opencensus.tags.TagValue.TagValueLong;
+import io.opencensus.tags.TagValue.TagValueString;
+import javax.annotation.concurrent.Immutable;
+
+/** {@link TagKey} paired with a value. */
+@Immutable
+public abstract class Tag {
+
+ /**
+ * Returns the tag's key.
+ *
+ * @return the tag's key.
+ */
+ public abstract TagKey getKey();
+
+ /**
+ * Returns the associated tag value.
+ *
+ * @return the associated tag value.
+ */
+ public abstract TagValue getValue();
+
+ Tag() {}
+
+ /**
+ * Applies a function to the tag's key and value. The function that is called depends on the type
+ * of the tag. This is similar to the visitor pattern. {@code match} also takes a function to
+ * handle the default case, for backwards compatibility when tag types are added. For example,
+ * this code serializes a {@code Tag} and tries to handle new tag types by calling {@code
+ * toString()}.
+ *
+ * <pre>{@code
+ * byte[] serializedValue =
+ * tag.match(
+ * stringTag -> serializeString(stringTag.getValue().asString()),
+ * longTag -> serializeLong(longTag.getValue()),
+ * booleanTag -> serializeBoolean(booleanTag.getValue()),
+ * unknownTag -> serializeString(unknownTag.toString()));
+ * }</pre>
+ *
+ * <p>Without lambdas:
+ *
+ * <pre><code>
+ * byte[] serializedValue =
+ * tag.match(
+ * new Function&lt;TagString, String&gt;() {
+ * {@literal @}Override
+ * public String apply(TagString stringTag) {
+ * return serializeString(stringTag.getValue().asString());
+ * }
+ * },
+ * new Function&lt;TagLong, String&gt;() {
+ * {@literal @}Override
+ * public String apply(TagLong longTag) {
+ * serializeLong(longTag.getValue());
+ * }
+ * },
+ * new Function&lt;TagBoolean, String&gt;() {
+ * {@literal @}Override
+ * public String apply(TagBoolean booleanTag) {
+ * serializeBoolean(booleanTag.getValue());
+ * }
+ * },
+ * new Function&lt;Tag, String&gt;() {
+ * {@literal @}Override
+ * public String apply(TagBoolean unknownTag) {
+ * serializeString(unknownTag.toString());
+ * }
+ * });
+ * </code></pre>
+ *
+ * @param stringFunction the function to call when the tag has a {@code String} value.
+ * @param longFunction the function to call when the tag has a {@code long} value.
+ * @param booleanFunction the function to call when the tag has a {@code boolean} value.
+ * @param defaultFunction the function to call when the tag has a value other than {@code String},
+ * {@code long}, or {@code boolean}.
+ * @param <T> The result type of the function.
+ * @return The result of calling the function that matches the tag's type.
+ */
+ public abstract <T> T match(
+ Function<? super TagString, T> stringFunction,
+ Function<? super TagLong, T> longFunction,
+ Function<? super TagBoolean, T> booleanFunction,
+ Function<? super Tag, T> defaultFunction);
+
+ /** A tag with a {@code String} key and value. */
+ @Immutable
+ @AutoValue
+ public abstract static class TagString extends Tag {
+ TagString() {}
+
+ /**
+ * Creates a {@code TagString} from the given {@code String} key and value.
+ *
+ * @param key the tag key.
+ * @param value the tag value.
+ * @return a {@code TagString} with the given key and value.
+ */
+ public static TagString create(TagKeyString key, TagValueString value) {
+ return new AutoValue_Tag_TagString(key, value);
+ }
+
+ @Override
+ public abstract TagKeyString getKey();
+
+ @Override
+ public abstract TagValueString getValue();
+
+ @Override
+ public final <T> T match(
+ Function<? super TagString, T> stringFunction,
+ Function<? super TagLong, T> longFunction,
+ Function<? super TagBoolean, T> booleanFunction,
+ Function<? super Tag, T> defaultFunction) {
+ return stringFunction.apply(this);
+ }
+ }
+
+ /** A tag with a {@code long} key and value. */
+ @Immutable
+ @AutoValue
+ public abstract static class TagLong extends Tag {
+ TagLong() {}
+
+ /**
+ * Creates a {@code TagLong} from the given {@code long} key and value.
+ *
+ * @param key the tag key.
+ * @param value the tag value.
+ * @return a {@code TagLong} with the given key and value.
+ */
+ public static TagLong create(TagKeyLong key, TagValueLong value) {
+ return new AutoValue_Tag_TagLong(key, value);
+ }
+
+ @Override
+ public abstract TagKeyLong getKey();
+
+ @Override
+ public abstract TagValueLong getValue();
+
+ @Override
+ public final <T> T match(
+ Function<? super TagString, T> stringFunction,
+ Function<? super TagLong, T> longFunction,
+ Function<? super TagBoolean, T> booleanFunction,
+ Function<? super Tag, T> defaultFunction) {
+ return longFunction.apply(this);
+ }
+ }
+
+ /** A tag with a {@code boolean} key and value. */
+ @Immutable
+ @AutoValue
+ public abstract static class TagBoolean extends Tag {
+ TagBoolean() {}
+
+ /**
+ * Creates a {@code TagBoolean} from the given {@code boolean} key and value.
+ *
+ * @param key the tag key.
+ * @param value the tag value.
+ * @return a {@code TagBoolean} with the given key and value.
+ */
+ public static TagBoolean create(TagKeyBoolean key, TagValueBoolean value) {
+ return new AutoValue_Tag_TagBoolean(key, value);
+ }
+
+ @Override
+ public abstract TagKeyBoolean getKey();
+
+ @Override
+ public abstract TagValueBoolean getValue();
+
+ @Override
+ public final <T> T match(
+ Function<? super TagString, T> stringFunction,
+ Function<? super TagLong, T> longFunction,
+ Function<? super TagBoolean, T> booleanFunction,
+ Function<? super Tag, T> defaultFunction) {
+ return booleanFunction.apply(this);
+ }
+ }
+}
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..be8f0dc1
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagContext.java
@@ -0,0 +1,99 @@
+/*
+ * 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.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multiset;
+import io.opencensus.tags.TagValue.TagValueString;
+import java.util.Iterator;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A map from keys to values 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.
+ *
+ * <p>Keys have type {@link TagKey}. Values have type {@link TagValueString}, though the library
+ * will support more types in the future, including {@code long} and {@code boolean}.
+ */
+@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}.
+ */
+ // 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(Object other) {
+ if (!(other instanceof TagContext)) {
+ return false;
+ }
+ TagContext otherTags = (TagContext) other;
+ Iterator<Tag> iter1 = getIterator();
+ Iterator<Tag> iter2 = otherTags.getIterator();
+ Multiset<Tag> tags1 =
+ iter1 == null
+ ? ImmutableMultiset.<Tag>of()
+ : HashMultiset.create(Lists.newArrayList(iter1));
+ Multiset<Tag> tags2 =
+ iter2 == null
+ ? ImmutableMultiset.<Tag>of()
+ : HashMultiset.create(Lists.newArrayList(iter2));
+ return tags1.equals(tags2);
+ }
+
+ @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..bbd6da7c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagContextBuilder.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.tags;
+
+import io.opencensus.common.Scope;
+import io.opencensus.tags.TagKey.TagKeyBoolean;
+import io.opencensus.tags.TagKey.TagKeyLong;
+import io.opencensus.tags.TagKey.TagKeyString;
+import io.opencensus.tags.TagValue.TagValueBoolean;
+import io.opencensus.tags.TagValue.TagValueLong;
+import io.opencensus.tags.TagValue.TagValueString;
+
+/** Builder for the {@link TagContext} class. */
+// TODO(sebright): Decide what to do when 'put' is called with a key that has the same name as an
+// existing key, but a different type. We currently keep both keys.
+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 value to set for the given key.
+ * @return this
+ */
+ public abstract TagContextBuilder put(TagKeyString key, TagValueString value);
+
+ /**
+ * Adds the key/value pair regardless of whether the key is present.
+ *
+ * @param key the {@code TagKey} which will be set.
+ * @param value the value to set for the given key.
+ * @return this
+ */
+ public abstract TagContextBuilder put(TagKeyLong key, TagValueLong value);
+
+ /**
+ * Adds the key/value pair regardless of whether the key is present.
+ *
+ * @param key the {@code TagKey} which will be set.
+ * @param value the value to set for the given key.
+ * @return this
+ */
+ public abstract TagContextBuilder put(TagKeyBoolean key, TagValueBoolean value);
+
+ /**
+ * Removes the key if it exists.
+ *
+ * @param key the {@code TagKey} which will be removed.
+ * @return this
+ */
+ public abstract TagContextBuilder remove(TagKey key);
+
+ /**
+ * Creates a {@code TagContext} from this builder.
+ *
+ * @return a {@code TagContext} with the same tags as this builder.
+ */
+ 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.
+ */
+ 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..943f12d7
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagKey.java
@@ -0,0 +1,244 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import io.opencensus.common.Function;
+import io.opencensus.internal.StringUtil;
+import io.opencensus.tags.TagValue.TagValueString;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * A key to a value stored in a {@link TagContext}.
+ *
+ * <p>There is one {@code TagKey} subclass corresponding to each tag value type, so that each key
+ * can only be paired with a single type of value. For example, {@link TagKeyString} can only be
+ * used to set {@link TagValueString} values in a {@code 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 ensures
+ * that the keys have consistent value types and prevents key names from being validated multiple
+ * times.
+ */
+@Immutable
+public abstract class TagKey {
+ /** The maximum length for a tag key name. The value is {@value #MAX_LENGTH}. */
+ public static final int MAX_LENGTH = 255;
+
+ TagKey() {}
+
+ public abstract String getName();
+
+ /**
+ * Applies a function to the {@code TagKey} subclass. The function that is called depends on the
+ * type of the tag key. This is similar to the visitor pattern. {@code match} also takes a
+ * function to handle the default case, for backwards compatibility when tag types are added. For
+ * example, this code creates a {@code Tag} from a {@code TagKey}. It handles new tag types by
+ * logging an error and returning a {@code TagKeyString}.
+ *
+ * <pre>{@code
+ * Tag tag =
+ * tagKey.match(
+ * stringKey -> TagString.create(stringKey, TagValueString.create("string value")),
+ * longKey -> TagLong.create(longKey, 100L),
+ * booleanKey -> TagBoolean.create(booleanKey, true),
+ * unknownKey -> {
+ * logger.log(Level.WARNING, "Unknown tag key type: " + unknownKey.toString());
+ * return TagString.create(
+ * TagKeyString.create(unknownKey.getName()),
+ * TagValueString.create("string value"));
+ * });
+ * }</pre>
+ *
+ * <p>Without lambdas:
+ *
+ * <pre><code>
+ * Tag tag =
+ * tagKey.match(
+ * new Function&lt;TagKeyString, Tag&gt;() {
+ * {@literal @}Override
+ * public Tag apply(TagKeyString stringKey) {
+ * return TagString.create(stringKey, TagValueString.create("string value"));
+ * }
+ * },
+ * new Function&lt;TagKeyLong, Tag&gt;() {
+ * {@literal @}Override
+ * public Tag apply(TagKeyLong longKey) {
+ * return TagLong.create(longKey, 100L);
+ * }
+ * },
+ * new Function&lt;TagKeyBoolean, Tag&gt;() {
+ * {@literal @}Override
+ * public Tag apply(TagKeyBoolean booleanKey) {
+ * return TagBoolean.create(booleanKey, true);
+ * }
+ * },
+ * new Function&lt;TagKey, Tag&gt;() {
+ * {@literal @}Override
+ * public Tag apply(TagKey unknownKey) {
+ * logger.log(Level.WARNING, "Unknown tag key type: " + unknownKey.toString());
+ * return TagString.create(
+ * TagKeyString.create(unknownKey.getName()),
+ * TagValueString.create("string value"));
+ * }
+ * });
+ * </code></pre>
+ *
+ * @param stringFunction the function to call when the {@code TagKey} is a {@code TagKeyString}.
+ * @param longFunction the function to call when the {@code TagKey} is a {@code TagKeyLong}.
+ * @param booleanFunction the function to call when the {@code TagKey} is a {@code TagKeyBoolean}.
+ * @param defaultFunction the function to call when the tag key has a type other than {@code
+ * String}, {@code long}, or {@code boolean}.
+ * @param <T> The result type of the function.
+ * @return The result of calling the function that matches the tag key's type.
+ */
+ // TODO(sebright): Should we make this public in the first release?
+ public abstract <T> T match(
+ Function<? super TagKeyString, T> stringFunction,
+ Function<? super TagKeyLong, T> longFunction,
+ Function<? super TagKeyBoolean, T> booleanFunction,
+ Function<? super TagKey, T> defaultFunction);
+
+ /**
+ * 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.length() <= MAX_LENGTH && StringUtil.isPrintableString(name);
+ }
+
+ /** A {@code TagKey} for values of type {@code String}. */
+ @Immutable
+ @AutoValue
+ public abstract static class TagKeyString extends TagKey {
+
+ /**
+ * Constructs a {@code TagKeyString} 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 TagKeyString} with the given name.
+ * @throws IllegalArgumentException if the name is not valid.
+ */
+ public static TagKeyString create(String name) {
+ // TODO(sebright): Should we disallow an empty name?
+ checkArgument(isValid(name));
+ return new AutoValue_TagKey_TagKeyString(name);
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super TagKeyString, T> stringFunction,
+ Function<? super TagKeyLong, T> longFunction,
+ Function<? super TagKeyBoolean, T> booleanFunction,
+ Function<? super TagKey, T> defaultFunction) {
+ return stringFunction.apply(this);
+ }
+ }
+
+ /**
+ * A {@code TagKey} for values of type {@code long}.
+ *
+ * <p>Note that {@link TagKeyLong} isn't supported by the implementation yet, so the factory
+ * method isn't exposed.
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class TagKeyLong extends TagKey {
+
+ /**
+ * Constructs a {@code TagKeyLong} 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 TagKeyLong} with the given name.
+ * @throws IllegalArgumentException if the name is not valid.
+ */
+ // TODO(sebright): Make this public once we support types other than String.
+ static TagKeyLong create(String name) {
+ checkArgument(isValid(name));
+ return new AutoValue_TagKey_TagKeyLong(name);
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super TagKeyString, T> stringFunction,
+ Function<? super TagKeyLong, T> longFunction,
+ Function<? super TagKeyBoolean, T> booleanFunction,
+ Function<? super TagKey, T> defaultFunction) {
+ return longFunction.apply(this);
+ }
+ }
+
+ /**
+ * A {@code TagKey} for values of type {@code boolean}.
+ *
+ * <p>Note that {@link TagKeyBoolean} isn't supported by the implementation yet, so the factory
+ * method isn't exposed.
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class TagKeyBoolean extends TagKey {
+
+ /**
+ * Constructs a {@code TagKeyBoolean} 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 TagKeyBoolean} with the given name.
+ * @throws IllegalArgumentException if the name is not valid.
+ */
+ // TODO(sebright): Make this public once we support types other than String.
+ static TagKeyBoolean create(String name) {
+ checkArgument(isValid(name));
+ return new AutoValue_TagKey_TagKeyBoolean(name);
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super TagKeyString, T> stringFunction,
+ Function<? super TagKeyLong, T> longFunction,
+ Function<? super TagKeyBoolean, T> booleanFunction,
+ Function<? super TagKey, T> defaultFunction) {
+ return booleanFunction.apply(this);
+ }
+ }
+}
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..7da713c5
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagValue.java
@@ -0,0 +1,182 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+import io.opencensus.common.Function;
+import io.opencensus.internal.StringUtil;
+import io.opencensus.tags.TagKey.TagKeyBoolean;
+import io.opencensus.tags.TagKey.TagKeyLong;
+import io.opencensus.tags.TagKey.TagKeyString;
+import javax.annotation.concurrent.Immutable;
+
+/** A validated tag value. */
+@Immutable
+public abstract class TagValue {
+
+ TagValue() {}
+
+ /**
+ * Applies a function to a tag value. The function that is called depends on the type of the
+ * value. This is similar to the visitor pattern. {@code match} also takes a function to handle
+ * the default case, for backwards compatibility when tag types are added.
+ *
+ * @param stringFunction the function to call when the tag value has type {@code String}.
+ * @param longFunction the function to call when the tag value has type {@code long}.
+ * @param booleanFunction the function to call when the tag value has type {@code boolean}.
+ * @param defaultFunction the function to call when the tag value has a type other than {@code
+ * String}, {@code long}, or {@code boolean}.
+ * @param <T> The result type of the function.
+ * @return The result of calling the function that matches the tag value's type.
+ */
+ public abstract <T> T match(
+ Function<? super TagValueString, T> stringFunction,
+ Function<? super TagValueLong, T> longFunction,
+ Function<? super TagValueBoolean, T> booleanFunction,
+ Function<? super TagValue, T> defaultFunction);
+
+ /**
+ * A validated tag value associated with a {@link TagKeyString}.
+ *
+ * <p>Validation ensures that the {@code String} has a maximum length of {@link #MAX_LENGTH} and
+ * contains only printable ASCII characters.
+ */
+ @Immutable
+ @AutoValue
+ public abstract static class TagValueString extends TagValue {
+ /** The maximum length for a {@code String} tag value. The value is {@value #MAX_LENGTH}. */
+ public static final int MAX_LENGTH = 256;
+
+ TagValueString() {}
+
+ /**
+ * Constructs a {@code TagValueString} 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.
+ */
+ public static TagValueString create(String value) {
+ Preconditions.checkArgument(isValid(value));
+ return new AutoValue_TagValue_TagValueString(value);
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super TagValueString, T> stringFunction,
+ Function<? super TagValueLong, T> longFunction,
+ Function<? super TagValueBoolean, T> booleanFunction,
+ Function<? super TagValue, T> defaultFunction) {
+ return stringFunction.apply(this);
+ }
+
+ /**
+ * Returns the tag value as a {@code String}.
+ *
+ * @return the tag value as a {@code String}.
+ */
+ 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 && StringUtil.isPrintableString(value);
+ }
+ }
+
+ /** A tag value associated with a {@link TagKeyLong}. */
+ @Immutable
+ @AutoValue
+ public abstract static class TagValueLong extends TagValue {
+
+ TagValueLong() {}
+
+ /**
+ * Constructs a {@code TagValueLong} from the given {@code long}.
+ *
+ * @param value the tag value.
+ */
+ public static TagValueLong create(long value) {
+ return new AutoValue_TagValue_TagValueLong(value);
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super TagValueString, T> stringFunction,
+ Function<? super TagValueLong, T> longFunction,
+ Function<? super TagValueBoolean, T> booleanFunction,
+ Function<? super TagValue, T> defaultFunction) {
+ return longFunction.apply(this);
+ }
+
+ /**
+ * Returns the tag value as a {@code long}.
+ *
+ * @return the tag value as a {@code long}.
+ */
+ public abstract long asLong();
+ }
+
+ /** A tag value associated with a {@link TagKeyBoolean}. */
+ @Immutable
+ @AutoValue
+ public abstract static class TagValueBoolean extends TagValue {
+ private static final TagValueBoolean TRUE_VALUE = createInternal(true);
+ private static final TagValueBoolean FALSE_VALUE = createInternal(false);
+
+ TagValueBoolean() {}
+
+ /**
+ * Constructs a {@code TagValueBoolean} from the given {@code boolean}.
+ *
+ * @param value the tag value.
+ */
+ public static TagValueBoolean create(boolean value) {
+ return value ? TRUE_VALUE : FALSE_VALUE;
+ }
+
+ private static TagValueBoolean createInternal(boolean value) {
+ return new AutoValue_TagValue_TagValueBoolean(value);
+ }
+
+ @Override
+ public final <T> T match(
+ Function<? super TagValueString, T> stringFunction,
+ Function<? super TagValueLong, T> longFunction,
+ Function<? super TagValueBoolean, T> booleanFunction,
+ Function<? super TagValue, T> defaultFunction) {
+ return booleanFunction.apply(this);
+ }
+
+ /**
+ * Returns the tag value as a {@code boolean}.
+ *
+ * @return the tag value as a {@code boolean}.
+ */
+ public abstract boolean asBoolean();
+ }
+}
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..1d786f71
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/Tagger.java
@@ -0,0 +1,78 @@
+/*
+ * 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}.
+ */
+public abstract class Tagger {
+
+ /**
+ * Returns an empty {@code TagContext}.
+ *
+ * @return an empty {@code TagContext}.
+ */
+ public abstract TagContext empty();
+
+ /**
+ * Returns the current {@code TagContext}.
+ *
+ * @return the current {@code TagContext}.
+ */
+ public abstract TagContext getCurrentTagContext();
+
+ /**
+ * Returns a new empty {@code Builder}.
+ *
+ * @return a new empty {@code Builder}.
+ */
+ public abstract TagContextBuilder emptyBuilder();
+
+ /**
+ * Returns a builder based on this {@code TagContext}.
+ *
+ * @return a builder based on this {@code TagContext}.
+ */
+ 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}.
+ */
+ public abstract TagContextBuilder currentBuilder();
+
+ /**
+ * Enters the scope of code where the given {@code TagContext} is in the current context 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.
+ */
+ 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..861aca74
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TaggingState.java
@@ -0,0 +1,40 @@
+/*
+ * 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}. */
+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.
+ */
+ 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.
+ */
+ // 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..5378fa5a
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/Tags.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.tags;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.internal.Provider;
+import io.opencensus.tags.propagation.TagPropagationComponent;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/** Class for accessing the default {@link TagsComponent}. */
+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}.
+ */
+ public static Tagger getTagger() {
+ return tagsComponent.getTagger();
+ }
+
+ /**
+ * Returns the default {@code TagPropagationComponent}.
+ *
+ * @return the default {@code TagPropagationComponent}.
+ */
+ 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}.
+ *
+ * @return the current {@code TaggingState}.
+ */
+ public static TaggingState getState() {
+ return tagsComponent.getState();
+ }
+
+ /**
+ * Sets the current {@code TaggingState}.
+ *
+ * <p>When no implementation is available, {@code setState} has no effect.
+ *
+ * @param state the new {@code TaggingState}.
+ */
+ public static void setState(TaggingState state) {
+ tagsComponent.setState(state);
+ }
+
+ // Any provider that may be used for TagsComponent can be added here.
+ @VisibleForTesting
+ static TagsComponent loadTagsComponent(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", 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", 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.getNoopTagsComponent();
+ }
+}
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..755173d8
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/TagsComponent.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+public abstract class TagsComponent {
+
+ /** Returns the {@link Tagger} for this implementation. */
+ public abstract Tagger getTagger();
+
+ /** Returns the {@link TagPropagationComponent} for this implementation. */
+ public abstract TagPropagationComponent getTagPropagationComponent();
+
+ /**
+ * Returns the current {@code TaggingState}.
+ *
+ * <p>When no implementation is available, {@code getState} always returns {@link
+ * TaggingState#DISABLED}.
+ *
+ * @return the current {@code TaggingState}.
+ */
+ public abstract TaggingState getState();
+
+ /**
+ * Sets the current {@code TaggingState}.
+ *
+ * <p>When no implementation is available, {@code setState} has no effect.
+ *
+ * @param state the new {@code TaggingState}.
+ */
+ 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..2a332f6d
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/package-info.java
@@ -0,0 +1,33 @@
+/*
+ * 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} are wrapped {@code String}s, but the values can have multiple types, such as {@code
+ * String}, {@code long}, and {@code boolean}. 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 after the API is updated to use a TagContext factory.
+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..e54e5d3d
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.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.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.
+ */
+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}.
+ */
+ public abstract byte[] toByteArray(TagContext tags);
+
+ /**
+ * 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 TagContextParseException if there is a parse error or the serialized {@code TagContext}
+ * contains invalid tags.
+ */
+ public abstract TagContext fromByteArray(byte[] bytes) throws TagContextParseException;
+}
diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagContextParseException.java b/api/src/main/java/io/opencensus/tags/propagation/TagContextParseException.java
new file mode 100644
index 00000000..0174c416
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextParseException.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.tags.propagation;
+
+import io.opencensus.tags.TagContext;
+
+/** Exception thrown when a {@link TagContext} cannot be parsed. */
+public final class TagContextParseException extends Exception {
+ private static final long serialVersionUID = 0L;
+
+ /**
+ * Constructs a new {@code TagContextParseException} with the given message.
+ *
+ * @param message a message describing the parse error.
+ */
+ public TagContextParseException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new {@code TagContextParseException} with the given message and cause.
+ *
+ * @param message a message describing the parse error.
+ * @param cause the cause of the parse error.
+ */
+ public TagContextParseException(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..c51a845c
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java
@@ -0,0 +1,31 @@
+/*
+ * 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. */
+// TODO(sebright): Add an HTTP serializer.
+public abstract class TagPropagationComponent {
+
+ /**
+ * Returns the {@link TagContextBinarySerializer} for this implementation.
+ *
+ * @return the {@code TagContextBinarySerializer} for this implementation.
+ */
+ 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..2baf1a2a
--- /dev/null
+++ b/api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+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}.
+ */
+ 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();
+ }
+ }
+}