diff options
author | Julien Desprez <jdesprez@google.com> | 2018-10-22 11:37:22 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2018-10-22 11:37:22 -0700 |
commit | 13217871fefa43f6d16fbb31b04e9904996d87d5 (patch) | |
tree | ede84fcf0a9687d4907ae5f8a4788271d62e0922 /api/src/main/java | |
parent | cfbefd32336596ea63784607e4106dc37ce0567f (diff) | |
parent | 6fbc3cf5a1a3369fd354c1e5d9f90c86e4bce0a4 (diff) | |
download | opencensus-java-13217871fefa43f6d16fbb31b04e9904996d87d5.tar.gz |
Merge remote-tracking branch 'aosp/upstream-master' into merge am: dd3cabeacc
am: 6fbc3cf5a1
Change-Id: I11b0ec1cf561d2a14da78e444b1594f167787fe6
Diffstat (limited to 'api/src/main/java')
117 files changed, 15196 insertions, 0 deletions
diff --git a/api/src/main/java/io/opencensus/common/Clock.java b/api/src/main/java/io/opencensus/common/Clock.java new file mode 100644 index 00000000..cd311935 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Clock.java @@ -0,0 +1,43 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/** + * Interface for getting the current time. + * + * @since 0.5 + */ +public abstract class Clock { + + /** + * Obtains the current instant from this clock. + * + * @return the current instant. + * @since 0.5 + */ + public abstract Timestamp now(); + + /** + * Returns a time measurement with nanosecond precision that can only be used to calculate elapsed + * time. + * + * @return a time measurement with nanosecond precision that can only be used to calculate elapsed + * time. + * @since 0.5 + */ + public abstract long nowNanos(); +} diff --git a/api/src/main/java/io/opencensus/common/Duration.java b/api/src/main/java/io/opencensus/common/Duration.java new file mode 100644 index 00000000..f46cd187 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Duration.java @@ -0,0 +1,136 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import static io.opencensus.common.TimeUtils.MAX_NANOS; +import static io.opencensus.common.TimeUtils.MAX_SECONDS; +import static io.opencensus.common.TimeUtils.MILLIS_PER_SECOND; +import static io.opencensus.common.TimeUtils.NANOS_PER_MILLI; + +import com.google.auto.value.AutoValue; +import java.util.concurrent.TimeUnit; +import javax.annotation.concurrent.Immutable; + +/** + * Represents a signed, fixed-length span of time represented as a count of seconds and fractions of + * seconds at nanosecond resolution. It is independent of any calendar and concepts like "day" or + * "month". Range is approximately +-10,000 years. + * + * @since 0.5 + */ +@Immutable +@AutoValue +public abstract class Duration implements Comparable<Duration> { + + /** + * Creates a new time duration from given seconds and nanoseconds. + * + * @param seconds Signed seconds of the span of time. Must be from -315,576,000,000 to + * +315,576,000,000 inclusive. + * @param nanos Signed fractions of a second at nanosecond resolution of the span of time. + * Durations less than one second are represented with a 0 `seconds` field and a positive or + * negative `nanos` field. For durations of one second or more, a non-zero value for the + * `nanos` field must be of the same sign as the `seconds` field. Must be from -999,999,999 to + * +999,999,999 inclusive. + * @return new {@code Duration} with specified fields. + * @throws IllegalArgumentException if the arguments are out of range or have inconsistent sign. + * @since 0.5 + */ + public static Duration create(long seconds, int nanos) { + if (seconds < -MAX_SECONDS) { + throw new IllegalArgumentException( + "'seconds' is less than minimum (" + -MAX_SECONDS + "): " + seconds); + } + if (seconds > MAX_SECONDS) { + throw new IllegalArgumentException( + "'seconds' is greater than maximum (" + MAX_SECONDS + "): " + seconds); + } + if (nanos < -MAX_NANOS) { + throw new IllegalArgumentException( + "'nanos' is less than minimum (" + -MAX_NANOS + "): " + nanos); + } + if (nanos > MAX_NANOS) { + throw new IllegalArgumentException( + "'nanos' is greater than maximum (" + MAX_NANOS + "): " + nanos); + } + if ((seconds < 0 && nanos > 0) || (seconds > 0 && nanos < 0)) { + throw new IllegalArgumentException( + "'seconds' and 'nanos' have inconsistent sign: seconds=" + seconds + ", nanos=" + nanos); + } + return new AutoValue_Duration(seconds, nanos); + } + + /** + * Creates a new {@code Duration} from given milliseconds. + * + * @param millis the duration in milliseconds. + * @return a new {@code Duration} from given milliseconds. + * @throws IllegalArgumentException if the number of milliseconds is out of the range that can be + * represented by {@code Duration}. + * @since 0.5 + */ + public static Duration fromMillis(long millis) { + long seconds = millis / MILLIS_PER_SECOND; + int nanos = (int) (millis % MILLIS_PER_SECOND * NANOS_PER_MILLI); + return Duration.create(seconds, nanos); + } + + /** + * Converts a {@link Duration} to milliseconds. + * + * @return the milliseconds representation of this {@code Duration}. + * @since 0.13 + */ + public long toMillis() { + return TimeUnit.SECONDS.toMillis(getSeconds()) + TimeUnit.NANOSECONDS.toMillis(getNanos()); + } + + /** + * Returns the number of seconds in the {@code Duration}. + * + * @return the number of seconds in the {@code Duration}. + * @since 0.5 + */ + public abstract long getSeconds(); + + /** + * Returns the number of nanoseconds in the {@code Duration}. + * + * @return the number of nanoseconds in the {@code Duration}. + * @since 0.5 + */ + public abstract int getNanos(); + + /** + * Compares this {@code Duration} to the specified {@code Duration}. + * + * @param otherDuration the other {@code Duration} to compare to, not {@code null}. + * @return the comparator value: zero if equal, negative if this duration is smaller than + * otherDuration, positive if larger. + * @throws NullPointerException if otherDuration is {@code null}. + */ + @Override + public int compareTo(Duration otherDuration) { + int cmp = TimeUtils.compareLongs(getSeconds(), otherDuration.getSeconds()); + if (cmp != 0) { + return cmp; + } + return TimeUtils.compareLongs(getNanos(), otherDuration.getNanos()); + } + + Duration() {} +} diff --git a/api/src/main/java/io/opencensus/common/ExperimentalApi.java b/api/src/main/java/io/opencensus/common/ExperimentalApi.java new file mode 100644 index 00000000..7a4da7c7 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/ExperimentalApi.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates a public API that can change at any time, and has no guarantee of API stability and + * backward-compatibility. + * + * <p>Usage guidelines: + * + * <ol> + * <li>This annotation is used only on public API. Internal interfaces should not use it. + * <li>After OpenCensus has gained API stability, this annotation can only be added to new API. + * Adding it to an existing API is considered API-breaking. + * <li>Removing this annotation from an API gives it stable status. + * </ol> + * + * @since 0.8 + */ +@Internal +@Retention(RetentionPolicy.SOURCE) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE +}) +@Documented +public @interface ExperimentalApi { + /** + * Context information such as links to discussion thread, tracking issue etc. + * + * @since 0.8 + */ + String value() default ""; +} diff --git a/api/src/main/java/io/opencensus/common/Function.java b/api/src/main/java/io/opencensus/common/Function.java new file mode 100644 index 00000000..a9ed5a9e --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Function.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/** + * Used to specify matching functions for use encoding tagged unions (i.e. sum types) in Java. See + * {@link io.opencensus.trace.AttributeValue#match} for an example of its use. + * + * <p>Note: This class is based on the java.util.Function class added in Java 1.8. We cannot use the + * Function from Java 1.8 because this library is Java 1.6 compatible. + * + * @since 0.5 + */ +public interface Function<A, B> { + + /** + * Applies the function to the given argument. + * + * @param arg the argument to the function. + * @return the result of the function. + * @since 0.5 + */ + B apply(A arg); +} diff --git a/api/src/main/java/io/opencensus/common/Functions.java b/api/src/main/java/io/opencensus/common/Functions.java new file mode 100644 index 00000000..ea3457ca --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Functions.java @@ -0,0 +1,130 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * Commonly used {@link Function} instances. + * + * @since 0.5 + */ +public final class Functions { + private Functions() {} + + private static final Function<Object, /*@Nullable*/ Void> RETURN_NULL = + new Function<Object, /*@Nullable*/ Void>() { + @Override + @javax.annotation.Nullable + public Void apply(Object ignored) { + return null; + } + }; + + private static final Function<Object, Void> THROW_ILLEGAL_ARGUMENT_EXCEPTION = + new Function<Object, Void>() { + @Override + public Void apply(Object ignored) { + throw new IllegalArgumentException(); + } + }; + + private static final Function<Object, Void> THROW_ASSERTION_ERROR = + new Function<Object, Void>() { + @Override + public Void apply(Object ignored) { + throw new AssertionError(); + } + }; + + private static final Function<Object, /*@Nullable*/ String> RETURN_TO_STRING = + new Function<Object, /*@Nullable*/ String>() { + @Override + public /*@Nullable*/ String apply(Object input) { + return input == null ? null : input.toString(); + } + }; + + /** + * A {@code Function} that always ignores its argument and returns {@code null}. + * + * @return a {@code Function} that always ignores its argument and returns {@code null}. + * @since 0.5 + */ + public static <T> Function<Object, /*@Nullable*/ T> returnNull() { + // It is safe to cast a producer of Void to anything, because Void is always null. + @SuppressWarnings("unchecked") + Function<Object, /*@Nullable*/ T> function = (Function<Object, /*@Nullable*/ T>) RETURN_NULL; + return function; + } + + /** + * A {@code Function} that always ignores its argument and returns a constant value. + * + * @return a {@code Function} that always ignores its argument and returns a constant value. + * @since 0.5 + */ + public static <T> Function<Object, T> returnConstant(final T constant) { + return new Function<Object, T>() { + @Override + public T apply(Object ignored) { + return constant; + } + }; + } + + /** + * A {@code Function} that always returns the {@link #toString()} value of the input. + * + * @return a {@code Function} that always returns the {@link #toString()} value of the input. + * @since 0.17 + */ + public static Function<Object, /*@Nullable*/ String> returnToString() { + return RETURN_TO_STRING; + } + + /** + * A {@code Function} that always ignores its argument and throws an {@link + * IllegalArgumentException}. + * + * @return a {@code Function} that always ignores its argument and throws an {@link + * IllegalArgumentException}. + * @since 0.5 + */ + public static <T> Function<Object, T> throwIllegalArgumentException() { + // It is safe to cast this function to have any return type, since it never returns a result. + @SuppressWarnings("unchecked") + Function<Object, T> function = (Function<Object, T>) THROW_ILLEGAL_ARGUMENT_EXCEPTION; + return function; + } + + /** + * A {@code Function} that always ignores its argument and throws an {@link AssertionError}. + * + * @return a {@code Function} that always ignores its argument and throws an {@code + * AssertionError}. + * @since 0.6 + */ + public static <T> Function<Object, T> throwAssertionError() { + // It is safe to cast this function to have any return type, since it never returns a result. + @SuppressWarnings("unchecked") + Function<Object, T> function = (Function<Object, T>) THROW_ASSERTION_ERROR; + return function; + } +} diff --git a/api/src/main/java/io/opencensus/common/Internal.java b/api/src/main/java/io/opencensus/common/Internal.java new file mode 100644 index 00000000..d84fba20 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Internal.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates a program element (class, method, package etc) which is internal to OpenCensus, not + * part of the public API, and should not be used by users of the OpenCensus library. + * + * @since 0.5 + */ +@Internal +@Retention(RetentionPolicy.SOURCE) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE +}) +@Documented +public @interface Internal {} diff --git a/api/src/main/java/io/opencensus/common/NonThrowingCloseable.java b/api/src/main/java/io/opencensus/common/NonThrowingCloseable.java new file mode 100644 index 00000000..30d07ac7 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/NonThrowingCloseable.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import java.io.Closeable; + +/** + * An {@link Closeable} which cannot throw a checked exception. + * + * <p>This is useful because such a reversion otherwise requires the caller to catch the + * (impossible) Exception in the try-with-resources. + * + * <p>Example of usage: + * + * <pre> + * try (NonThrowingAutoCloseable ctx = tryEnter()) { + * ... + * } + * </pre> + * + * @deprecated {@link Scope} is a better match for operations involving the current context. + * @since 0.5 + */ +@Deprecated +public interface NonThrowingCloseable extends Closeable { + @Override + void close(); +} diff --git a/api/src/main/java/io/opencensus/common/OpenCensusLibraryInformation.java b/api/src/main/java/io/opencensus/common/OpenCensusLibraryInformation.java new file mode 100644 index 00000000..3f659c12 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/OpenCensusLibraryInformation.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/** + * Class holder for all common constants (such as the version) for the OpenCensus Java library. + * + * @since 0.8 + */ +@ExperimentalApi +public final class OpenCensusLibraryInformation { + + /** + * The current version of the OpenCensus Java library. + * + * @since 0.8 + */ + public static final String VERSION = "0.17.0-SNAPSHOT"; // CURRENT_OPENCENSUS_VERSION + + private OpenCensusLibraryInformation() {} +} diff --git a/api/src/main/java/io/opencensus/common/Scope.java b/api/src/main/java/io/opencensus/common/Scope.java new file mode 100644 index 00000000..de954f50 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Scope.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/** + * A {@link java.io.Closeable} that represents a change to the current context over a scope of code. + * {@link Scope#close} cannot throw a checked exception. + * + * <p>Example of usage: + * + * <pre> + * try (Scope ctx = tryEnter()) { + * ... + * } + * </pre> + * + * @since 0.6 + */ +@SuppressWarnings("deprecation") +public interface Scope extends NonThrowingCloseable { + @Override + void close(); +} diff --git a/api/src/main/java/io/opencensus/common/ServerStats.java b/api/src/main/java/io/opencensus/common/ServerStats.java new file mode 100644 index 00000000..42efa1f2 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/ServerStats.java @@ -0,0 +1,86 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import com.google.auto.value.AutoValue; +import javax.annotation.concurrent.Immutable; + +/** + * A representation of stats measured on the server side. + * + * @since 0.16 + */ +@Immutable +@AutoValue +public abstract class ServerStats { + + ServerStats() {} + + /** + * Returns Load Balancer latency, a latency observed at Load Balancer. + * + * @return Load Balancer latency in nanoseconds. + * @since 0.16 + */ + public abstract long getLbLatencyNs(); + + /** + * Returns Service latency, a latency observed at Server. + * + * @return Service latency in nanoseconds. + * @since 0.16 + */ + public abstract long getServiceLatencyNs(); + + /** + * Returns Trace options, a set of bits indicating properties of trace. + * + * @return Trace options a set of bits indicating properties of trace. + * @since 0.16 + */ + public abstract byte getTraceOption(); + + /** + * Creates new {@link ServerStats} from specified parameters. + * + * @param lbLatencyNs Represents request processing latency observed on Load Balancer. It is + * measured in nanoseconds. Must not be less than 0. Value of 0 represents that the latency is + * not measured. + * @param serviceLatencyNs Represents request processing latency observed on Server. It is + * measured in nanoseconds. Must not be less than 0. Value of 0 represents that the latency is + * not measured. + * @param traceOption Represents set of bits to indicate properties of trace. Currently it used + * only the least signification bit to represent sampling of the request on the server side. + * Other bits are ignored. + * @return new {@code ServerStats} with specified fields. + * @throws IllegalArgumentException if the arguments are out of range. + * @since 0.16 + */ + public static ServerStats create(long lbLatencyNs, long serviceLatencyNs, byte traceOption) { + + if (lbLatencyNs < 0) { + throw new IllegalArgumentException("'getLbLatencyNs' is less than zero: " + lbLatencyNs); + } + + if (serviceLatencyNs < 0) { + throw new IllegalArgumentException( + "'getServiceLatencyNs' is less than zero: " + serviceLatencyNs); + } + + return new AutoValue_ServerStats(lbLatencyNs, serviceLatencyNs, traceOption); + } +} diff --git a/api/src/main/java/io/opencensus/common/ServerStatsDeserializationException.java b/api/src/main/java/io/opencensus/common/ServerStatsDeserializationException.java new file mode 100644 index 00000000..2332733c --- /dev/null +++ b/api/src/main/java/io/opencensus/common/ServerStatsDeserializationException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/** + * Exception thrown when a {@link ServerStats} cannot be parsed. + * + * @since 0.16 + */ +public final class ServerStatsDeserializationException extends Exception { + private static final long serialVersionUID = 0L; + + /** + * Constructs a new {@code ServerStatsDeserializationException} with the given message. + * + * @param message a message describing the error. + * @since 0.16 + */ + public ServerStatsDeserializationException(String message) { + super(message); + } + + /** + * Constructs a new {@code ServerStatsDeserializationException} with the given message and cause. + * + * @param message a message describing the error. + * @param cause the cause of the error. + * @since 0.16 + */ + public ServerStatsDeserializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/io/opencensus/common/ServerStatsEncoding.java b/api/src/main/java/io/opencensus/common/ServerStatsEncoding.java new file mode 100644 index 00000000..024a93f8 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/ServerStatsEncoding.java @@ -0,0 +1,125 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A service class to encode/decode {@link ServerStats} as defined by the spec. + * + * <p>See <a + * href="https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/CensusServerStatsEncoding.md">opencensus-server-stats-specs</a> + * for encoding {@code ServerStats} + * + * <p>Use {@code ServerStatsEncoding.toBytes(ServerStats stats)} to encode. + * + * <p>Use {@code ServerStatsEncoding.parseBytes(byte[] serialized)} to decode. + * + * @since 0.16 + */ +public final class ServerStatsEncoding { + + private ServerStatsEncoding() {} + + /** + * The current encoding version. The value is {@value #CURRENT_VERSION} + * + * @since 0.16 + */ + public static final byte CURRENT_VERSION = (byte) 0; + + /** + * Encodes the {@link ServerStats} as per the Opencensus Summary Span specification. + * + * @param stats {@code ServerStats} to encode. + * @return encoded byte array. + * @since 0.16 + */ + public static byte[] toBytes(ServerStats stats) { + // Should this be optimized to not include invalid values? + + ByteBuffer bb = ByteBuffer.allocate(ServerStatsFieldEnums.getTotalSize() + 1); + bb.order(ByteOrder.LITTLE_ENDIAN); + + // put version + bb.put(CURRENT_VERSION); + + bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_LB_LATENCY_ID.value()); + bb.putLong(stats.getLbLatencyNs()); + + bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_SERVICE_LATENCY_ID.value()); + bb.putLong(stats.getServiceLatencyNs()); + + bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_TRACE_OPTION_ID.value()); + bb.put(stats.getTraceOption()); + return bb.array(); + } + + /** + * Decodes serialized byte array to create {@link ServerStats} as per Opencensus Summary Span + * specification. + * + * @param serialized encoded {@code ServerStats} in byte array. + * @return decoded {@code ServerStats}. null if decoding fails. + * @since 0.16 + */ + public static ServerStats parseBytes(byte[] serialized) + throws ServerStatsDeserializationException { + final ByteBuffer bb = ByteBuffer.wrap(serialized); + bb.order(ByteOrder.LITTLE_ENDIAN); + long serviceLatencyNs = 0L; + long lbLatencyNs = 0L; + byte traceOption = (byte) 0; + + // Check the version first. + if (!bb.hasRemaining()) { + throw new ServerStatsDeserializationException("Serialized ServerStats buffer is empty"); + } + byte version = bb.get(); + + if (version > CURRENT_VERSION || version < 0) { + throw new ServerStatsDeserializationException("Invalid ServerStats version: " + version); + } + + while (bb.hasRemaining()) { + ServerStatsFieldEnums.Id id = ServerStatsFieldEnums.Id.valueOf((int) bb.get() & 0xFF); + if (id == null) { + // Skip remaining; + bb.position(bb.limit()); + } else { + switch (id) { + case SERVER_STATS_LB_LATENCY_ID: + lbLatencyNs = bb.getLong(); + break; + case SERVER_STATS_SERVICE_LATENCY_ID: + serviceLatencyNs = bb.getLong(); + break; + case SERVER_STATS_TRACE_OPTION_ID: + traceOption = bb.get(); + break; + } + } + } + try { + return ServerStats.create(lbLatencyNs, serviceLatencyNs, traceOption); + } catch (IllegalArgumentException e) { + throw new ServerStatsDeserializationException( + "Serialized ServiceStats contains invalid values: " + e.getMessage()); + } + } +} diff --git a/api/src/main/java/io/opencensus/common/ServerStatsFieldEnums.java b/api/src/main/java/io/opencensus/common/ServerStatsFieldEnums.java new file mode 100644 index 00000000..ff3cfda9 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/ServerStatsFieldEnums.java @@ -0,0 +1,159 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import java.util.TreeMap; +import javax.annotation.Nullable; + +/** + * A Enum representation for Ids and Size for attributes of {@code ServerStats}. + * + * <p>See <a + * href="https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/CensusServerStatsEncoding.md">opencensus-server-stats-specs</a> + * for the field ids and their length defined for Server Stats + * + * @since 0.16 + */ +public final class ServerStatsFieldEnums { + + /** + * Available Ids for {@code ServerStats} attributes. + * + * @since 0.16 + */ + public enum Id { + /** + * Id for Latency observed at Load Balancer. + * + * @since 0.16 + */ + SERVER_STATS_LB_LATENCY_ID(0), + /** + * Id for Latency observed at Server. + * + * @since 0.16 + */ + SERVER_STATS_SERVICE_LATENCY_ID(1), + /** + * Id for Trace options. + * + * @since 0.16 + */ + SERVER_STATS_TRACE_OPTION_ID(2); + + private final int value; + + private Id(int value) { + this.value = value; + } + + /** + * Returns the numerical value of the {@link Id}. + * + * @return the numerical value of the {@code Id}. + * @since 0.16 + */ + public int value() { + return value; + } + + private static final TreeMap<Integer, Id> map = new TreeMap<Integer, Id>(); + + static { + for (Id id : Id.values()) { + map.put(id.value, id); + } + } + + /** + * Returns the {@link Id} representing the value value of the id. + * + * @param value integer value for which {@code Id} is being requested. + * @return the numerical value of the id. null if the id is not valid + * @since 0.16 + */ + @Nullable + public static Id valueOf(int value) { + return map.get(value); + } + } + + /** + * Size for each attributes in {@code ServerStats}. + * + * @since 0.16 + */ + public enum Size { + /** + * Number of bytes used to represent latency observed at Load Balancer. + * + * @since 0.16 + */ + SERVER_STATS_LB_LATENCY_SIZE(8), + /** + * Number of bytes used to represent latency observed at Server. + * + * @since 0.16 + */ + SERVER_STATS_SERVICE_LATENCY_SIZE(8), + /** + * Number of bytes used to represent Trace option. + * + * @since 0.16 + */ + SERVER_STATS_TRACE_OPTION_SIZE(1); + + private final int value; + + private Size(int value) { + this.value = value; + } + + /** + * Returns the numerical value of the {@link Size}. + * + * @return the numerical value of the {@code Size}. + * @since 0.16 + */ + public int value() { + return value; + } + } + + private static final int TOTALSIZE = computeTotalSize(); + + private ServerStatsFieldEnums() {} + + private static int computeTotalSize() { + int sum = 0; + for (Size sizeValue : Size.values()) { + sum += sizeValue.value(); + sum += 1; // For Id + } + return sum; + } + + /** + * Returns the total size required to encode the {@code ServerStats}. + * + * @return the total size required to encode all fields in {@code ServerStats}. + * @since 0.16 + */ + public static int getTotalSize() { + return TOTALSIZE; + } +} diff --git a/api/src/main/java/io/opencensus/common/TimeUtils.java b/api/src/main/java/io/opencensus/common/TimeUtils.java new file mode 100644 index 00000000..db119e2e --- /dev/null +++ b/api/src/main/java/io/opencensus/common/TimeUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import java.math.BigInteger; + +/** Util class for {@link Timestamp} and {@link Duration}. */ +final class TimeUtils { + static final long MAX_SECONDS = 315576000000L; + static final int MAX_NANOS = 999999999; + static final long MILLIS_PER_SECOND = 1000L; + static final long NANOS_PER_MILLI = 1000 * 1000; + static final long NANOS_PER_SECOND = NANOS_PER_MILLI * MILLIS_PER_SECOND; + + private TimeUtils() {} + + /** + * Compares two longs. This functionality is provided by {@code Long.compare(long, long)} in Java + * 7. + */ + static int compareLongs(long x, long y) { + if (x < y) { + return -1; + } else if (x == y) { + return 0; + } else { + return 1; + } + } + + private static final BigInteger MAX_LONG_VALUE = BigInteger.valueOf(Long.MAX_VALUE); + private static final BigInteger MIN_LONG_VALUE = BigInteger.valueOf(Long.MIN_VALUE); + + /** + * Adds two longs and throws an {@link ArithmeticException} if the result overflows. This + * functionality is provided by {@code Math.addExact(long, long)} in Java 8. + */ + static long checkedAdd(long x, long y) { + BigInteger sum = BigInteger.valueOf(x).add(BigInteger.valueOf(y)); + if (sum.compareTo(MAX_LONG_VALUE) > 0 || sum.compareTo(MIN_LONG_VALUE) < 0) { + throw new ArithmeticException("Long sum overflow: x=" + x + ", y=" + y); + } + return x + y; + } +} diff --git a/api/src/main/java/io/opencensus/common/Timestamp.java b/api/src/main/java/io/opencensus/common/Timestamp.java new file mode 100644 index 00000000..d17b3fd8 --- /dev/null +++ b/api/src/main/java/io/opencensus/common/Timestamp.java @@ -0,0 +1,200 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +import static io.opencensus.common.TimeUtils.MAX_NANOS; +import static io.opencensus.common.TimeUtils.MAX_SECONDS; +import static io.opencensus.common.TimeUtils.MILLIS_PER_SECOND; +import static io.opencensus.common.TimeUtils.NANOS_PER_MILLI; +import static io.opencensus.common.TimeUtils.NANOS_PER_SECOND; + +import com.google.auto.value.AutoValue; +import java.math.BigDecimal; +import java.math.RoundingMode; +import javax.annotation.concurrent.Immutable; + +/** + * A representation of an instant in time. The instant is the number of nanoseconds after the number + * of seconds since the Unix Epoch. + * + * <p>Use {@code Tracing.getClock().now()} to get the current timestamp since epoch + * (1970-01-01T00:00:00Z). + * + * @since 0.5 + */ +@Immutable +@AutoValue +public abstract class Timestamp implements Comparable<Timestamp> { + + Timestamp() {} + + /** + * Creates a new timestamp from given seconds and nanoseconds. + * + * @param seconds Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be + * from from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive. + * @param nanos Non-negative fractions of a second at nanosecond resolution. Negative second + * values with fractions must still have non-negative nanos values that count forward in time. + * Must be from 0 to 999,999,999 inclusive. + * @return new {@code Timestamp} with specified fields. + * @throws IllegalArgumentException if the arguments are out of range. + * @since 0.5 + */ + public static Timestamp create(long seconds, int nanos) { + if (seconds < -MAX_SECONDS) { + throw new IllegalArgumentException( + "'seconds' is less than minimum (" + -MAX_SECONDS + "): " + seconds); + } + if (seconds > MAX_SECONDS) { + throw new IllegalArgumentException( + "'seconds' is greater than maximum (" + MAX_SECONDS + "): " + seconds); + } + if (nanos < 0) { + throw new IllegalArgumentException("'nanos' is less than zero: " + nanos); + } + if (nanos > MAX_NANOS) { + throw new IllegalArgumentException( + "'nanos' is greater than maximum (" + MAX_NANOS + "): " + nanos); + } + return new AutoValue_Timestamp(seconds, nanos); + } + + /** + * Creates a new timestamp from the given milliseconds. + * + * @param epochMilli the timestamp represented in milliseconds since epoch. + * @return new {@code Timestamp} with specified fields. + * @throws IllegalArgumentException if the number of milliseconds is out of the range that can be + * represented by {@code Timestamp}. + * @since 0.5 + */ + public static Timestamp fromMillis(long epochMilli) { + long secs = floorDiv(epochMilli, MILLIS_PER_SECOND); + int mos = (int) floorMod(epochMilli, MILLIS_PER_SECOND); + return create(secs, (int) (mos * NANOS_PER_MILLI)); // Safe int * NANOS_PER_MILLI + } + + /** + * Returns the number of seconds since the Unix Epoch represented by this timestamp. + * + * @return the number of seconds since the Unix Epoch. + * @since 0.5 + */ + public abstract long getSeconds(); + + /** + * Returns the number of nanoseconds after the number of seconds since the Unix Epoch represented + * by this timestamp. + * + * @return the number of nanoseconds after the number of seconds since the Unix Epoch. + * @since 0.5 + */ + public abstract int getNanos(); + + /** + * Returns a {@code Timestamp} calculated as this {@code Timestamp} plus some number of + * nanoseconds. + * + * @param nanosToAdd the nanos to add, positive or negative. + * @return the calculated {@code Timestamp}. For invalid inputs, a {@code Timestamp} of zero is + * returned. + * @throws ArithmeticException if numeric overflow occurs. + * @since 0.5 + */ + public Timestamp addNanos(long nanosToAdd) { + return plus(0, nanosToAdd); + } + + /** + * Returns a {@code Timestamp} calculated as this {@code Timestamp} plus some {@code Duration}. + * + * @param duration the {@code Duration} to add. + * @return a {@code Timestamp} with the specified {@code Duration} added. + * @since 0.5 + */ + public Timestamp addDuration(Duration duration) { + return plus(duration.getSeconds(), duration.getNanos()); + } + + /** + * Returns a {@link Duration} calculated as: {@code this - timestamp}. + * + * @param timestamp the {@code Timestamp} to subtract. + * @return the calculated {@code Duration}. For invalid inputs, a {@code Duration} of zero is + * returned. + * @since 0.5 + */ + public Duration subtractTimestamp(Timestamp timestamp) { + long durationSeconds = getSeconds() - timestamp.getSeconds(); + int durationNanos = getNanos() - timestamp.getNanos(); + if (durationSeconds < 0 && durationNanos > 0) { + durationSeconds += 1; + durationNanos = (int) (durationNanos - NANOS_PER_SECOND); + } else if (durationSeconds > 0 && durationNanos < 0) { + durationSeconds -= 1; + durationNanos = (int) (durationNanos + NANOS_PER_SECOND); + } + return Duration.create(durationSeconds, durationNanos); + } + + /** + * Compares this {@code Timestamp} to the specified {@code Timestamp}. + * + * @param otherTimestamp the other {@code Timestamp} to compare to, not {@code null}. + * @return the comparator value: zero if equal, negative if this timestamp happens before + * otherTimestamp, positive if after. + * @throws NullPointerException if otherTimestamp is {@code null}. + */ + @Override + public int compareTo(Timestamp otherTimestamp) { + int cmp = TimeUtils.compareLongs(getSeconds(), otherTimestamp.getSeconds()); + if (cmp != 0) { + return cmp; + } + return TimeUtils.compareLongs(getNanos(), otherTimestamp.getNanos()); + } + + // Returns a Timestamp with the specified duration added. + private Timestamp plus(long secondsToAdd, long nanosToAdd) { + if ((secondsToAdd | nanosToAdd) == 0) { + return this; + } + long epochSec = TimeUtils.checkedAdd(getSeconds(), secondsToAdd); + epochSec = TimeUtils.checkedAdd(epochSec, nanosToAdd / NANOS_PER_SECOND); + nanosToAdd = nanosToAdd % NANOS_PER_SECOND; + long nanoAdjustment = getNanos() + nanosToAdd; // safe int + NANOS_PER_SECOND + return ofEpochSecond(epochSec, nanoAdjustment); + } + + // Returns a Timestamp calculated using seconds from the epoch and nanosecond fraction of + // second (arbitrary number of nanoseconds). + private static Timestamp ofEpochSecond(long epochSecond, long nanoAdjustment) { + long secs = TimeUtils.checkedAdd(epochSecond, floorDiv(nanoAdjustment, NANOS_PER_SECOND)); + int nos = (int) floorMod(nanoAdjustment, NANOS_PER_SECOND); + return create(secs, nos); + } + + // Returns the result of dividing x by y rounded using floor. + private static long floorDiv(long x, long y) { + return BigDecimal.valueOf(x).divide(BigDecimal.valueOf(y), 0, RoundingMode.FLOOR).longValue(); + } + + // Returns the floor modulus "x - (floorDiv(x, y) * y)" + private static long floorMod(long x, long y) { + return x - floorDiv(x, y) * y; + } +} diff --git a/api/src/main/java/io/opencensus/common/ToDoubleFunction.java b/api/src/main/java/io/opencensus/common/ToDoubleFunction.java new file mode 100644 index 00000000..6ace2f7c --- /dev/null +++ b/api/src/main/java/io/opencensus/common/ToDoubleFunction.java @@ -0,0 +1,41 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * Represents a function that produces a double-valued result. See {@link + * io.opencensus.metrics.MetricRegistry} for an example of its use. + * + * <p>Note: This class is based on the java.util.ToDoubleFunction class added in Java 1.8. We cannot + * use the Function from Java 1.8 because this library is Java 1.6 compatible. + * + * @since 0.16 + */ +public interface ToDoubleFunction</*@Nullable*/ T> { + + /** + * Applies this function to the given argument. + * + * @param value the function argument. + * @return the function result. + */ + double applyAsDouble(/*@Nullable*/ T value); +} diff --git a/api/src/main/java/io/opencensus/common/ToLongFunction.java b/api/src/main/java/io/opencensus/common/ToLongFunction.java new file mode 100644 index 00000000..cd2b68ed --- /dev/null +++ b/api/src/main/java/io/opencensus/common/ToLongFunction.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.common; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * Represents a function that produces a long-valued result. See {@link + * io.opencensus.metrics.MetricRegistry} for an example of its use. + * + * <p>Note: This class is based on the java.util.ToLongFunction class added in Java 1.8. We cannot + * use the Function from Java 1.8 because this library is Java 1.6 compatible. + * + * @since 0.16 + */ +public interface ToLongFunction</*@Nullable*/ T> { + /** + * Applies this function to the given argument. + * + * @param value the function argument. + * @return the function result. + */ + long applyAsLong(/*@Nullable*/ T value); +} diff --git a/api/src/main/java/io/opencensus/common/package-info.java b/api/src/main/java/io/opencensus/common/package-info.java new file mode 100644 index 00000000..1ebfd7cf --- /dev/null +++ b/api/src/main/java/io/opencensus/common/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Common API between different packages in this artifact. */ +package io.opencensus.common; diff --git a/api/src/main/java/io/opencensus/internal/DefaultVisibilityForTesting.java b/api/src/main/java/io/opencensus/internal/DefaultVisibilityForTesting.java new file mode 100644 index 00000000..e90a6573 --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/DefaultVisibilityForTesting.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that an element is package-private instead of private only for the purpose of testing. + * This annotation is only meant to be used as documentation in the source code. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ + ElementType.ANNOTATION_TYPE, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE +}) +public @interface DefaultVisibilityForTesting {} diff --git a/api/src/main/java/io/opencensus/internal/NoopScope.java b/api/src/main/java/io/opencensus/internal/NoopScope.java new file mode 100644 index 00000000..f4a8da07 --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/NoopScope.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import io.opencensus.common.Scope; + +/** A {@link Scope} that does nothing when it is created or closed. */ +public final class NoopScope implements Scope { + private static final Scope INSTANCE = new NoopScope(); + + private NoopScope() {} + + /** + * Returns a {@code NoopScope}. + * + * @return a {@code NoopScope}. + */ + public static Scope getInstance() { + return INSTANCE; + } + + @Override + public void close() {} +} diff --git a/api/src/main/java/io/opencensus/internal/Provider.java b/api/src/main/java/io/opencensus/internal/Provider.java new file mode 100644 index 00000000..8cfb7294 --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/Provider.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import java.util.ServiceConfigurationError; + +/** + * OpenCensus service provider mechanism. + * + * <pre>{@code + * // Initialize a variable using reflection. + * foo = Provider.createInstance( + * Class.forName("FooImpl", true, classLoader), Foo.class); + * }</pre> + */ +public final class Provider { + private Provider() {} + + /** + * Tries to create an instance of the given rawClass as a subclass of the given superclass. + * + * @param rawClass The class that is initialized. + * @param superclass The initialized class must be a subclass of this. + * @return an instance of the class given rawClass which is a subclass of the given superclass. + * @throws ServiceConfigurationError if any error happens. + */ + public static <T> T createInstance(Class<?> rawClass, Class<T> superclass) { + try { + return rawClass.asSubclass(superclass).getConstructor().newInstance(); + } catch (Exception e) { + throw new ServiceConfigurationError( + "Provider " + rawClass.getName() + " could not be instantiated.", e); + } + } +} diff --git a/api/src/main/java/io/opencensus/internal/StringUtils.java b/api/src/main/java/io/opencensus/internal/StringUtils.java new file mode 100644 index 00000000..717e333c --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/StringUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +/** Internal utility methods for working with tag keys, tag values, and metric names. */ +public final class StringUtils { + + /** + * Determines whether the {@code String} contains only printable characters. + * + * @param str the {@code String} to be validated. + * @return whether the {@code String} contains only printable characters. + */ + public static boolean isPrintableString(String str) { + for (int i = 0; i < str.length(); i++) { + if (!isPrintableChar(str.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean isPrintableChar(char ch) { + return ch >= ' ' && ch <= '~'; + } + + private StringUtils() {} +} diff --git a/api/src/main/java/io/opencensus/internal/Utils.java b/api/src/main/java/io/opencensus/internal/Utils.java new file mode 100644 index 00000000..df5c9840 --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/Utils.java @@ -0,0 +1,191 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import java.util.List; + +/*>>> +import org.checkerframework.checker.nullness.qual.NonNull; +*/ + +/** General internal utility methods. */ +public final class Utils { + + private Utils() {} + + /** + * Throws an {@link IllegalArgumentException} if the argument is false. This method is similar to + * {@code Preconditions.checkArgument(boolean, Object)} from Guava. + * + * @param isValid whether the argument check passed. + * @param errorMessage the message to use for the exception. Will be converted to a string using + * {@link String#valueOf(Object)}. + */ + public static void checkArgument( + boolean isValid, @javax.annotation.Nullable Object errorMessage) { + if (!isValid) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Throws an {@link IllegalArgumentException} if the argument is false. This method is similar to + * {@code Preconditions.checkArgument(boolean, Object)} from Guava. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or + * {@code errorMessageArgs} is null (don't let this happen) + */ + public static void checkArgument( + boolean expression, + String errorMessageTemplate, + @javax.annotation.Nullable Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Throws an {@link IllegalStateException} if the argument is false. This method is similar to + * {@code Preconditions.checkState(boolean, Object)} from Guava. + * + * @param isValid whether the state check passed. + * @param errorMessage the message to use for the exception. Will be converted to a string using + * {@link String#valueOf(Object)}. + */ + public static void checkState(boolean isValid, @javax.annotation.Nullable Object errorMessage) { + if (!isValid) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Validates an index in an array or other container. This method throws an {@link + * IllegalArgumentException} if the size is negative and throws an {@link + * IndexOutOfBoundsException} if the index is negative or greater than or equal to the size. This + * method is similar to {@code Preconditions.checkElementIndex(int, int)} from Guava. + * + * @param index the index to validate. + * @param size the size of the array or container. + */ + public static void checkIndex(int index, int size) { + if (size < 0) { + throw new IllegalArgumentException("Negative size: " + size); + } + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Index out of bounds: size=" + size + ", index=" + index); + } + } + + /** + * Throws a {@link NullPointerException} if the argument is null. This method is similar to {@code + * Preconditions.checkNotNull(Object, Object)} from Guava. + * + * @param arg the argument to check for null. + * @param errorMessage the message to use for the exception. Will be converted to a string using + * {@link String#valueOf(Object)}. + * @return the argument, if it passes the null check. + */ + public static <T /*>>> extends @NonNull Object*/> T checkNotNull( + T arg, @javax.annotation.Nullable Object errorMessage) { + if (arg == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return arg; + } + + /** + * Throws a {@link NullPointerException} if any of the list elements is null. + * + * @param list the argument list to check for null. + * @param errorMessage the message to use for the exception. Will be converted to a string using + * {@link String#valueOf(Object)}. + */ + public static <T /*>>> extends @NonNull Object*/> void checkListElementNotNull( + List<T> list, @javax.annotation.Nullable Object errorMessage) { + for (T element : list) { + if (element == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + } + } + + /** + * Compares two Objects for equality. This functionality is provided by {@code + * Objects.equal(Object, Object)} in Java 7. + */ + public static boolean equalsObjects( + @javax.annotation.Nullable Object x, @javax.annotation.Nullable Object y) { + return x == null ? y == null : x.equals(y); + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These are matched by + * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than + * placeholders, the unmatched arguments will be appended to the end of the formatted message in + * square braces. + * + * <p>Copied from {@code Preconditions.format(String, Object...)} from Guava + * + * @param template a non-null string containing 0 or more {@code %s} placeholders. + * @param args the arguments to be substituted into the message template. Arguments are converted + * to strings using {@link String#valueOf(Object)}. Arguments can be null. + */ + // Note that this is somewhat-improperly used from Verify.java as well. + private static String format(String template, @javax.annotation.Nullable Object... args) { + // If no arguments return the template. + if (args == null) { + return template; + } + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template, templateStart, template.length()); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } +} diff --git a/api/src/main/java/io/opencensus/internal/ZeroTimeClock.java b/api/src/main/java/io/opencensus/internal/ZeroTimeClock.java new file mode 100644 index 00000000..fda13e9e --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/ZeroTimeClock.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.internal; + +import io.opencensus.common.Clock; +import io.opencensus.common.Timestamp; +import javax.annotation.concurrent.Immutable; + +/** A {@link Clock} that always returns 0. */ +@Immutable +public final class ZeroTimeClock extends Clock { + private static final ZeroTimeClock INSTANCE = new ZeroTimeClock(); + private static final Timestamp ZERO_TIMESTAMP = Timestamp.create(0, 0); + + private ZeroTimeClock() {} + + /** + * Returns a {@code ZeroTimeClock}. + * + * @return a {@code ZeroTimeClock}. + */ + public static ZeroTimeClock getInstance() { + return INSTANCE; + } + + @Override + public Timestamp now() { + return ZERO_TIMESTAMP; + } + + @Override + public long nowNanos() { + return 0; + } +} diff --git a/api/src/main/java/io/opencensus/internal/package-info.java b/api/src/main/java/io/opencensus/internal/package-info.java new file mode 100644 index 00000000..5dd35b23 --- /dev/null +++ b/api/src/main/java/io/opencensus/internal/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Interfaces and implementations that are internal to OpenCensus. + * + * <p>All the content under this package and its subpackages are considered annotated with {@link + * io.opencensus.common.Internal}. + */ +@io.opencensus.common.Internal +package io.opencensus.internal; diff --git a/api/src/main/java/io/opencensus/metrics/DerivedDoubleGauge.java b/api/src/main/java/io/opencensus/metrics/DerivedDoubleGauge.java new file mode 100644 index 00000000..3aaca153 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/DerivedDoubleGauge.java @@ -0,0 +1,152 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import io.opencensus.common.ToDoubleFunction; +import io.opencensus.internal.Utils; +import java.lang.ref.WeakReference; +import java.util.List; +import javax.annotation.concurrent.ThreadSafe; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * Derived Double Gauge metric, to report instantaneous measurement of a double value. Gauges can go + * both up and down. The gauges values can be negative. + * + * <p>Example: Create a Gauge with an object and a callback function. + * + * <pre>{@code + * class YourClass { + * + * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry(); + * + * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc")); + * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound")); + * + * DerivedDoubleGauge gauge = metricRegistry.addDerivedDoubleGauge( + * "queue_size", "Pending jobs in a queue", "1", labelKeys); + * + * QueueManager queueManager = new QueueManager(); + * gauge.createTimeSeries(labelValues, queueManager, + * new ToDoubleFunction<QueueManager>() { + * {@literal @}Override + * public double applyAsDouble(QueueManager queue) { + * return queue.size(); + * } + * }); + * + * void doWork() { + * // Your code here. + * } + * } + * + * }</pre> + * + * @since 0.17 + */ +@ThreadSafe +public abstract class DerivedDoubleGauge { + /** + * Creates a {@code TimeSeries}. The value of a single point in the TimeSeries is observed from a + * callback function. This function is invoked whenever metrics are collected, meaning the + * reported value is up-to-date. It keeps a {@link WeakReference} to the object and it is the + * user's responsibility to manage the lifetime of the object. + * + * @param labelValues the list of label values. + * @param obj the state object from which the function derives a measurement. + * @param function the function to be called. + * @param <T> the type of the object upon which the function derives a measurement. + * @throws NullPointerException if {@code labelValues} is null OR any element of {@code + * labelValues} is null OR {@code function} is null. + * @throws IllegalArgumentException if different time series with the same labels already exists + * OR if number of {@code labelValues}s are not equal to the label keys. + * @since 0.17 + */ + public abstract <T> void createTimeSeries( + List<LabelValue> labelValues, + /*@Nullable*/ T obj, + ToDoubleFunction</*@Nullable*/ T> function); + + /** + * Removes the {@code TimeSeries} from the gauge metric, if it is present. + * + * @param labelValues the list of label values. + * @throws NullPointerException if {@code labelValues} is null. + * @since 0.17 + */ + public abstract void removeTimeSeries(List<LabelValue> labelValues); + + /** + * Removes all {@code TimeSeries} from the gauge metric. + * + * @since 0.17 + */ + public abstract void clear(); + + /** + * Returns the no-op implementation of the {@code DerivedDoubleGauge}. + * + * @return the no-op implementation of the {@code DerivedDoubleGauge}. + * @since 0.17 + */ + static DerivedDoubleGauge newNoopDerivedDoubleGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + return NoopDerivedDoubleGauge.create(name, description, unit, labelKeys); + } + + /** No-op implementations of DerivedDoubleGauge class. */ + private static final class NoopDerivedDoubleGauge extends DerivedDoubleGauge { + private final int labelKeysSize; + + static NoopDerivedDoubleGauge create( + String name, String description, String unit, List<LabelKey> labelKeys) { + return new NoopDerivedDoubleGauge(name, description, unit, labelKeys); + } + + /** Creates a new {@code NoopDerivedDoubleGauge}. */ + NoopDerivedDoubleGauge(String name, String description, String unit, List<LabelKey> labelKeys) { + Utils.checkNotNull(name, "name"); + Utils.checkNotNull(description, "description"); + Utils.checkNotNull(unit, "unit"); + Utils.checkListElementNotNull( + Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null."); + labelKeysSize = labelKeys.size(); + } + + @Override + public <T> void createTimeSeries( + List<LabelValue> labelValues, + /*@Nullable*/ T obj, + ToDoubleFunction</*@Nullable*/ T> function) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null."); + Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels."); + Utils.checkNotNull(function, "function"); + } + + @Override + public void removeTimeSeries(List<LabelValue> labelValues) { + Utils.checkNotNull(labelValues, "labelValues"); + } + + @Override + public void clear() {} + } +} diff --git a/api/src/main/java/io/opencensus/metrics/DerivedLongGauge.java b/api/src/main/java/io/opencensus/metrics/DerivedLongGauge.java new file mode 100644 index 00000000..621873f9 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/DerivedLongGauge.java @@ -0,0 +1,150 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import io.opencensus.common.ToLongFunction; +import io.opencensus.internal.Utils; +import java.lang.ref.WeakReference; +import java.util.List; +import javax.annotation.concurrent.ThreadSafe; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * Derived Long Gauge metric, to report instantaneous measurement of an int64 value. Gauges can go + * both up and down. The gauges values can be negative. + * + * <p>Example: Create a Gauge with an object and a callback function. + * + * <pre>{@code + * class YourClass { + * + * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry(); + * + * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc")); + * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound")); + * + * DerivedLongGauge gauge = metricRegistry.addDerivedLongGauge( + * "queue_size", "Pending jobs in a queue", "1", labelKeys); + * + * QueueManager queueManager = new QueueManager(); + * gauge.createTimeSeries(labelValues, queueManager, + * new ToLongFunction<QueueManager>() { + * {@literal @}Override + * public long applyAsLong(QueueManager queue) { + * return queue.size(); + * } + * }); + * + * void doWork() { + * // Your code here. + * } + * } + * + * }</pre> + * + * @since 0.17 + */ +@ThreadSafe +public abstract class DerivedLongGauge { + /** + * Creates a {@code TimeSeries}. The value of a single point in the TimeSeries is observed from a + * callback function. This function is invoked whenever metrics are collected, meaning the + * reported value is up-to-date. It keeps a {@link WeakReference} to the object and it is the + * user's responsibility to manage the lifetime of the object. + * + * @param labelValues the list of label values. + * @param obj the state object from which the function derives a measurement. + * @param function the function to be called. + * @param <T> the type of the object upon which the function derives a measurement. + * @throws NullPointerException if {@code labelValues} is null OR any element of {@code + * labelValues} is null OR {@code function} is null. + * @throws IllegalArgumentException if different time series with the same labels already exists + * OR if number of {@code labelValues}s are not equal to the label keys. + * @since 0.17 + */ + public abstract <T> void createTimeSeries( + List<LabelValue> labelValues, /*@Nullable*/ T obj, ToLongFunction</*@Nullable*/ T> function); + + /** + * Removes the {@code TimeSeries} from the gauge metric, if it is present. + * + * @param labelValues the list of label values. + * @throws NullPointerException if {@code labelValues} is null. + * @since 0.17 + */ + public abstract void removeTimeSeries(List<LabelValue> labelValues); + + /** + * Removes all {@code TimeSeries} from the gauge metric. + * + * @since 0.17 + */ + public abstract void clear(); + + /** + * Returns the no-op implementation of the {@code DerivedLongGauge}. + * + * @return the no-op implementation of the {@code DerivedLongGauge}. + * @since 0.17 + */ + static DerivedLongGauge newNoopDerivedLongGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + return NoopDerivedLongGauge.create(name, description, unit, labelKeys); + } + + /** No-op implementations of DerivedLongGauge class. */ + private static final class NoopDerivedLongGauge extends DerivedLongGauge { + private final int labelKeysSize; + + static NoopDerivedLongGauge create( + String name, String description, String unit, List<LabelKey> labelKeys) { + return new NoopDerivedLongGauge(name, description, unit, labelKeys); + } + + /** Creates a new {@code NoopDerivedLongGauge}. */ + NoopDerivedLongGauge(String name, String description, String unit, List<LabelKey> labelKeys) { + Utils.checkNotNull(name, "name"); + Utils.checkNotNull(description, "description"); + Utils.checkNotNull(unit, "unit"); + Utils.checkListElementNotNull( + Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null."); + labelKeysSize = labelKeys.size(); + } + + @Override + public <T> void createTimeSeries( + List<LabelValue> labelValues, + /*@Nullable*/ T obj, + ToLongFunction</*@Nullable*/ T> function) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null."); + Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels."); + Utils.checkNotNull(function, "function"); + } + + @Override + public void removeTimeSeries(List<LabelValue> labelValues) { + Utils.checkNotNull(labelValues, "labelValues"); + } + + @Override + public void clear() {} + } +} diff --git a/api/src/main/java/io/opencensus/metrics/DoubleGauge.java b/api/src/main/java/io/opencensus/metrics/DoubleGauge.java new file mode 100644 index 00000000..32759973 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/DoubleGauge.java @@ -0,0 +1,213 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import io.opencensus.internal.Utils; +import java.util.List; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Double Gauge metric, to report instantaneous measurement of a double value. Gauges can go both up + * and down. The gauges values can be negative. + * + * <p>Example 1: Create a Gauge with default labels. + * + * <pre>{@code + * class YourClass { + * + * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry(); + * + * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc")); + * + * DoubleGauge gauge = metricRegistry.addDoubleGauge("queue_size", + * "Pending jobs", "1", labelKeys); + * + * // It is recommended to keep a reference of a point for manual operations. + * DoublePoint defaultPoint = gauge.getDefaultTimeSeries(); + * + * void doWork() { + * // Your code here. + * defaultPoint.add(10); + * } + * + * } + * }</pre> + * + * <p>Example 2: You can also use labels(keys and values) to track different types of metric. + * + * <pre>{@code + * class YourClass { + * + * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry(); + * + * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc")); + * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound")); + * + * DoubleGauge gauge = metricRegistry.addDoubleGauge("queue_size", + * "Pending jobs", "1", labelKeys); + * + * // It is recommended to keep a reference of a point for manual operations. + * DoublePoint inboundPoint = gauge.getOrCreateTimeSeries(labelValues); + * + * void doSomeWork() { + * // Your code here. + * inboundPoint.set(15); + * } + * + * } + * }</pre> + * + * @since 0.17 + */ +@ThreadSafe +public abstract class DoubleGauge { + + /** + * Creates a {@code TimeSeries} and returns a {@code DoublePoint} if the specified {@code + * labelValues} is not already associated with this gauge, else returns an existing {@code + * DoublePoint}. + * + * <p>It is recommended to keep a reference to the DoublePoint instead of always calling this + * method for manual operations. + * + * @param labelValues the list of label values. The number of label values must be the same to + * that of the label keys passed to {@link MetricRegistry#addDoubleGauge}. + * @return a {@code DoublePoint} the value of single gauge. + * @throws NullPointerException if {@code labelValues} is null OR any element of {@code + * labelValues} is null. + * @throws IllegalArgumentException if number of {@code labelValues}s are not equal to the label + * keys. + * @since 0.17 + */ + public abstract DoublePoint getOrCreateTimeSeries(List<LabelValue> labelValues); + + /** + * Returns a {@code DoublePoint} for a gauge with all labels not set, or default labels. + * + * @return a {@code DoublePoint} for a gauge with all labels not set, or default labels. + * @since 0.17 + */ + public abstract DoublePoint getDefaultTimeSeries(); + + /** + * Removes the {@code TimeSeries} from the gauge metric, if it is present. i.e. references to + * previous {@code DoublePoint} objects are invalid (not part of the metric). + * + * @param labelValues the list of label values. + * @throws NullPointerException if {@code labelValues} is null or any element of {@code + * labelValues} is null. + * @since 0.17 + */ + public abstract void removeTimeSeries(List<LabelValue> labelValues); + + /** + * Removes all {@code TimeSeries} from the gauge metric. i.e. references to all previous {@code + * DoublePoint} objects are invalid (not part of the metric). + * + * @since 0.17 + */ + public abstract void clear(); + + /** + * Returns the no-op implementation of the {@code DoubleGauge}. + * + * @return the no-op implementation of the {@code DoubleGauge}. + * @since 0.17 + */ + static DoubleGauge newNoopDoubleGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + return NoopDoubleGauge.create(name, description, unit, labelKeys); + } + + /** + * The value of a single point in the Gauge.TimeSeries. + * + * @since 0.17 + */ + public abstract static class DoublePoint { + + /** + * Adds the given value to the current value. The values can be negative. + * + * @param amt the value to add + * @since 0.17 + */ + public abstract void add(double amt); + + /** + * Sets the given value. + * + * @param val the new value. + * @since 0.17 + */ + public abstract void set(double val); + } + + /** No-op implementations of DoubleGauge class. */ + private static final class NoopDoubleGauge extends DoubleGauge { + private final int labelKeysSize; + + static NoopDoubleGauge create( + String name, String description, String unit, List<LabelKey> labelKeys) { + return new NoopDoubleGauge(name, description, unit, labelKeys); + } + + /** Creates a new {@code NoopDoublePoint}. */ + NoopDoubleGauge(String name, String description, String unit, List<LabelKey> labelKeys) { + Utils.checkNotNull(name, "name"); + Utils.checkNotNull(description, "description"); + Utils.checkNotNull(unit, "unit"); + Utils.checkListElementNotNull( + Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null."); + labelKeysSize = labelKeys.size(); + } + + @Override + public NoopDoublePoint getOrCreateTimeSeries(List<LabelValue> labelValues) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null."); + Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels."); + return NoopDoublePoint.INSTANCE; + } + + @Override + public NoopDoublePoint getDefaultTimeSeries() { + return NoopDoublePoint.INSTANCE; + } + + @Override + public void removeTimeSeries(List<LabelValue> labelValues) { + Utils.checkNotNull(labelValues, "labelValues"); + } + + @Override + public void clear() {} + + /** No-op implementations of DoublePoint class. */ + private static final class NoopDoublePoint extends DoublePoint { + private static final NoopDoublePoint INSTANCE = new NoopDoublePoint(); + + private NoopDoublePoint() {} + + @Override + public void add(double amt) {} + + @Override + public void set(double val) {} + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/LabelKey.java b/api/src/main/java/io/opencensus/metrics/LabelKey.java new file mode 100644 index 00000000..efc51e64 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/LabelKey.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import javax.annotation.concurrent.Immutable; + +/** + * The key of a {@code Label} associated with a {@code MetricDescriptor}. + * + * @since 0.15 + */ +@ExperimentalApi +@Immutable +@AutoValue +public abstract class LabelKey { + + LabelKey() {} + + /** + * Creates a {@link LabelKey}. + * + * @param key the key of a {@code Label}. + * @param description a human-readable description of what this label key represents. + * @return a {@code LabelKey}. + * @since 0.17 + */ + public static LabelKey create(String key, String description) { + return new AutoValue_LabelKey(key, description); + } + + /** + * Returns the key of this {@link LabelKey}. + * + * @return the key. + * @since 0.17 + */ + public abstract String getKey(); + + /** + * Returns the description of this {@link LabelKey}. + * + * @return the description. + * @since 0.17 + */ + public abstract String getDescription(); +} diff --git a/api/src/main/java/io/opencensus/metrics/LabelValue.java b/api/src/main/java/io/opencensus/metrics/LabelValue.java new file mode 100644 index 00000000..e5708655 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/LabelValue.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * The value of a {@code Label} associated with a {@code TimeSeries}. + * + * @since 0.15 + */ +@ExperimentalApi +@Immutable +@AutoValue +public abstract class LabelValue { + + LabelValue() {} + + /** + * Creates a {@link LabelValue}. + * + * @param value the value of a {@code Label}. {@code null} value indicates an unset {@code + * LabelValue}. + * @return a {@code LabelValue}. + * @since 0.17 + */ + public static LabelValue create(@Nullable String value) { + return new AutoValue_LabelValue(value); + } + + /** + * Returns the value of this {@link LabelValue}. Returns {@code null} if the value is unset and + * supposed to be ignored. + * + * @return the value. + * @since 0.17 + */ + @Nullable + public abstract String getValue(); +} diff --git a/api/src/main/java/io/opencensus/metrics/LongGauge.java b/api/src/main/java/io/opencensus/metrics/LongGauge.java new file mode 100644 index 00000000..1d4489c9 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/LongGauge.java @@ -0,0 +1,205 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import io.opencensus.internal.Utils; +import java.util.List; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Long Gauge metric, to report instantaneous measurement of an int64 value. Gauges can go both up + * and down. The gauges values can be negative. + * + * <p>Example 1: Create a Gauge with default labels. + * + * <pre>{@code + * class YourClass { + * + * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry(); + * + * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc")); + * + * LongGauge gauge = metricRegistry.addLongGauge("queue_size", "Pending jobs", "1", labelKeys); + * + * // It is recommended to keep a reference of a point for manual operations. + * LongPoint defaultPoint = gauge.getDefaultTimeSeries(); + * + * void doWork() { + * // Your code here. + * defaultPoint.add(10); + * } + * + * } + * }</pre> + * + * <p>Example 2: You can also use labels(keys and values) to track different types of metric. + * + * <pre>{@code + * class YourClass { + * + * private static final MetricRegistry metricRegistry = Metrics.getMetricRegistry(); + * + * List<LabelKey> labelKeys = Arrays.asList(LabelKey.create("Name", "desc")); + * List<LabelValue> labelValues = Arrays.asList(LabelValue.create("Inbound")); + * + * LongGauge gauge = metricRegistry.addLongGauge("queue_size", "Pending jobs", "1", labelKeys); + * + * // It is recommended to keep a reference of a point for manual operations. + * LongPoint inboundPoint = gauge.getOrCreateTimeSeries(labelValues); + * + * void doSomeWork() { + * // Your code here. + * inboundPoint.set(15); + * } + * + * } + * }</pre> + * + * @since 0.17 + */ +@ThreadSafe +public abstract class LongGauge { + + /** + * Creates a {@code TimeSeries} and returns a {@code LongPoint} if the specified {@code + * labelValues} is not already associated with this gauge, else returns an existing {@code + * LongPoint}. + * + * <p>It is recommended to keep a reference to the LongPoint instead of always calling this method + * for manual operations. + * + * @param labelValues the list of label values. The number of label values must be the same to + * that of the label keys passed to {@link MetricRegistry#addLongGauge}. + * @return a {@code LongPoint} the value of single gauge. + * @throws NullPointerException if {@code labelValues} is null OR any element of {@code + * labelValues} is null. + * @throws IllegalArgumentException if number of {@code labelValues}s are not equal to the label + * keys passed to {@link MetricRegistry#addLongGauge}. + * @since 0.17 + */ + public abstract LongPoint getOrCreateTimeSeries(List<LabelValue> labelValues); + + /** + * Returns a {@code LongPoint} for a gauge with all labels not set, or default labels. + * + * @return a {@code LongPoint} for a gauge with all labels not set, or default labels. + * @since 0.17 + */ + public abstract LongPoint getDefaultTimeSeries(); + + /** + * Removes the {@code TimeSeries} from the gauge metric, if it is present. i.e. references to + * previous {@code LongPoint} objects are invalid (not part of the metric). + * + * @param labelValues the list of label values. + * @throws NullPointerException if {@code labelValues} is null. + * @since 0.17 + */ + public abstract void removeTimeSeries(List<LabelValue> labelValues); + + /** + * Removes all {@code TimeSeries} from the gauge metric. i.e. references to all previous {@code + * LongPoint} objects are invalid (not part of the metric). + * + * @since 0.17 + */ + public abstract void clear(); + + /** + * Returns the no-op implementation of the {@code LongGauge}. + * + * @return the no-op implementation of the {@code LongGauge}. + * @since 0.17 + */ + static LongGauge newNoopLongGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + return NoopLongGauge.create(name, description, unit, labelKeys); + } + + /** + * The value of a single point in the Gauge.TimeSeries. + * + * @since 0.17 + */ + public abstract static class LongPoint { + + /** + * Adds the given value to the current value. The values can be negative. + * + * @param amt the value to add + * @since 0.17 + */ + public abstract void add(long amt); + + /** + * Sets the given value. + * + * @param val the new value. + * @since 0.17 + */ + public abstract void set(long val); + } + + /** No-op implementations of LongGauge class. */ + private static final class NoopLongGauge extends LongGauge { + private final int labelKeysSize; + + static NoopLongGauge create( + String name, String description, String unit, List<LabelKey> labelKeys) { + return new NoopLongGauge(name, description, unit, labelKeys); + } + + /** Creates a new {@code NoopLongPoint}. */ + NoopLongGauge(String name, String description, String unit, List<LabelKey> labelKeys) { + labelKeysSize = labelKeys.size(); + } + + @Override + public NoopLongPoint getOrCreateTimeSeries(List<LabelValue> labelValues) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelValues, "labelValues"), "labelValue element should not be null."); + Utils.checkArgument(labelKeysSize == labelValues.size(), "Incorrect number of labels."); + return NoopLongPoint.INSTANCE; + } + + @Override + public NoopLongPoint getDefaultTimeSeries() { + return NoopLongPoint.INSTANCE; + } + + @Override + public void removeTimeSeries(List<LabelValue> labelValues) { + Utils.checkNotNull(labelValues, "labelValues"); + } + + @Override + public void clear() {} + + /** No-op implementations of LongPoint class. */ + private static final class NoopLongPoint extends LongPoint { + private static final NoopLongPoint INSTANCE = new NoopLongPoint(); + + private NoopLongPoint() {} + + @Override + public void add(long amt) {} + + @Override + public void set(long val) {} + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/MetricRegistry.java b/api/src/main/java/io/opencensus/metrics/MetricRegistry.java new file mode 100644 index 00000000..5be15594 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/MetricRegistry.java @@ -0,0 +1,156 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import io.opencensus.common.ExperimentalApi; +import io.opencensus.common.ToDoubleFunction; +import io.opencensus.common.ToLongFunction; +import io.opencensus.internal.Utils; +import java.util.List; + +/** + * Creates and manages your application's set of metrics. The default implementation of this creates + * a {@link io.opencensus.metrics.export.MetricProducer} and registers it to the global {@link + * io.opencensus.metrics.export.MetricProducerManager}. + * + * @since 0.17 + */ +@ExperimentalApi +public abstract class MetricRegistry { + /** + * Builds a new long gauge to be added to the registry. This is more convenient form when you want + * to manually increase and decrease values as per your service requirements. + * + * @param name the name of the metric. + * @param description the description of the metric. + * @param unit the unit of the metric. + * @param labelKeys the list of the label keys. + * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys} + * is null OR {@code name}, {@code description}, {@code unit} is null. + * @throws IllegalArgumentException if different metric with the same name already registered. + * @since 0.17 + */ + @ExperimentalApi + public abstract LongGauge addLongGauge( + String name, String description, String unit, List<LabelKey> labelKeys); + + /** + * Builds a new double gauge to be added to the registry. This is more convenient form when you + * want to manually increase and decrease values as per your service requirements. + * + * @param name the name of the metric. + * @param description the description of the metric. + * @param unit the unit of the metric. + * @param labelKeys the list of the label keys. + * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys} + * is null OR {@code name}, {@code description}, {@code unit} is null. + * @throws IllegalArgumentException if different metric with the same name already registered. + * @since 0.17 + */ + @ExperimentalApi + public abstract DoubleGauge addDoubleGauge( + String name, String description, String unit, List<LabelKey> labelKeys); + + /** + * Builds a new derived long gauge to be added to the registry. This is more convenient form when + * you want to define a gauge by executing a {@link ToLongFunction} on an object. + * + * @param name the name of the metric. + * @param description the description of the metric. + * @param unit the unit of the metric. + * @param labelKeys the list of the label keys. + * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys} + * is null OR {@code name}, {@code description}, {@code unit} is null. + * @throws IllegalArgumentException if different metric with the same name already registered. + * @since 0.17 + */ + @ExperimentalApi + public abstract DerivedLongGauge addDerivedLongGauge( + String name, String description, String unit, List<LabelKey> labelKeys); + + /** + * Builds a new derived double gauge to be added to the registry. This is more convenient form + * when you want to define a gauge by executing a {@link ToDoubleFunction} on an object. + * + * @param name the name of the metric. + * @param description the description of the metric. + * @param unit the unit of the metric. + * @param labelKeys the list of the label keys. + * @throws NullPointerException if {@code labelKeys} is null OR any element of {@code labelKeys} + * is null OR {@code name}, {@code description}, {@code unit} is null. + * @throws IllegalArgumentException if different metric with the same name already registered. + * @since 0.17 + */ + @ExperimentalApi + public abstract DerivedDoubleGauge addDerivedDoubleGauge( + String name, String description, String unit, List<LabelKey> labelKeys); + + static MetricRegistry newNoopMetricRegistry() { + return new NoopMetricRegistry(); + } + + private static final class NoopMetricRegistry extends MetricRegistry { + + @Override + public LongGauge addLongGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null."); + return LongGauge.newNoopLongGauge( + Utils.checkNotNull(name, "name"), + Utils.checkNotNull(description, "description"), + Utils.checkNotNull(unit, "unit"), + labelKeys); + } + + @Override + public DoubleGauge addDoubleGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null."); + return DoubleGauge.newNoopDoubleGauge( + Utils.checkNotNull(name, "name"), + Utils.checkNotNull(description, "description"), + Utils.checkNotNull(unit, "unit"), + labelKeys); + } + + @Override + public DerivedLongGauge addDerivedLongGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null."); + return DerivedLongGauge.newNoopDerivedLongGauge( + Utils.checkNotNull(name, "name"), + Utils.checkNotNull(description, "description"), + Utils.checkNotNull(unit, "unit"), + labelKeys); + } + + @Override + public DerivedDoubleGauge addDerivedDoubleGauge( + String name, String description, String unit, List<LabelKey> labelKeys) { + Utils.checkListElementNotNull( + Utils.checkNotNull(labelKeys, "labelKeys"), "labelKey element should not be null."); + return DerivedDoubleGauge.newNoopDerivedDoubleGauge( + Utils.checkNotNull(name, "name"), + Utils.checkNotNull(description, "description"), + Utils.checkNotNull(unit, "unit"), + labelKeys); + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/Metrics.java b/api/src/main/java/io/opencensus/metrics/Metrics.java new file mode 100644 index 00000000..920a4a88 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/Metrics.java @@ -0,0 +1,96 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import io.opencensus.common.ExperimentalApi; +import io.opencensus.internal.DefaultVisibilityForTesting; +import io.opencensus.internal.Provider; +import io.opencensus.metrics.export.ExportComponent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Class for accessing the default {@link MetricsComponent}. + * + * @since 0.17 + */ +@ExperimentalApi +public final class Metrics { + private static final Logger logger = Logger.getLogger(Metrics.class.getName()); + private static final MetricsComponent metricsComponent = + loadMetricsComponent(MetricsComponent.class.getClassLoader()); + + /** + * Returns the global {@link ExportComponent}. + * + * @return the global {@code ExportComponent}. + * @since 0.17 + */ + public static ExportComponent getExportComponent() { + return metricsComponent.getExportComponent(); + } + + /** + * Returns the global {@link MetricRegistry}. + * + * <p>This {@code MetricRegistry} is already added to the global {@link + * io.opencensus.metrics.export.MetricProducerManager}. + * + * @return the global {@code MetricRegistry}. + * @since 0.17 + */ + public static MetricRegistry getMetricRegistry() { + return metricsComponent.getMetricRegistry(); + } + + // Any provider that may be used for MetricsComponent can be added here. + @DefaultVisibilityForTesting + static MetricsComponent loadMetricsComponent(@Nullable ClassLoader classLoader) { + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impl.metrics.MetricsComponentImpl", /*initialize=*/ true, classLoader), + MetricsComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load full implementation for MetricsComponent, now trying to load lite " + + "implementation.", + e); + } + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impllite.metrics.MetricsComponentImplLite", + /*initialize=*/ true, + classLoader), + MetricsComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load lite implementation for MetricsComponent, now using default " + + "implementation for MetricsComponent.", + e); + } + return MetricsComponent.newNoopMetricsComponent(); + } + + private Metrics() {} +} diff --git a/api/src/main/java/io/opencensus/metrics/MetricsComponent.java b/api/src/main/java/io/opencensus/metrics/MetricsComponent.java new file mode 100644 index 00000000..3a992306 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/MetricsComponent.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics; + +import io.opencensus.common.ExperimentalApi; +import io.opencensus.metrics.export.ExportComponent; + +/** + * Class that holds the implementation instance for {@link ExportComponent}. + * + * @since 0.17 + */ +@ExperimentalApi +public abstract class MetricsComponent { + + /** + * Returns the {@link ExportComponent} with the provided implementation. If no implementation is + * provided then no-op implementations will be used. + * + * @return the {@link ExportComponent} implementation. + * @since 0.17 + */ + public abstract ExportComponent getExportComponent(); + + /** + * Returns the {@link MetricRegistry} with the provided implementation. + * + * @return the {@link MetricRegistry} implementation. + * @since 0.17 + */ + public abstract MetricRegistry getMetricRegistry(); + + /** + * Returns an instance that contains no-op implementations for all the instances. + * + * @return an instance that contains no-op implementations for all the instances. + */ + static MetricsComponent newNoopMetricsComponent() { + return new NoopMetricsComponent(); + } + + private static final class NoopMetricsComponent extends MetricsComponent { + private static final ExportComponent EXPORT_COMPONENT = + ExportComponent.newNoopExportComponent(); + private static final MetricRegistry METRIC_REGISTRY = MetricRegistry.newNoopMetricRegistry(); + + @Override + public ExportComponent getExportComponent() { + return EXPORT_COMPONENT; + } + + @Override + public MetricRegistry getMetricRegistry() { + return METRIC_REGISTRY; + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/export/Distribution.java b/api/src/main/java/io/opencensus/metrics/export/Distribution.java new file mode 100644 index 00000000..d55f101c --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/Distribution.java @@ -0,0 +1,345 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.common.Function; +import io.opencensus.common.Timestamp; +import io.opencensus.internal.Utils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * {@link Distribution} contains summary statistics for a population of values. It optionally + * contains a histogram representing the distribution of those values across a set of buckets. + * + * @since 0.17 + */ +@ExperimentalApi +@AutoValue +@Immutable +public abstract class Distribution { + + Distribution() {} + + /** + * Creates a {@link Distribution}. + * + * @param count the count of the population values. + * @param sum the sum of the population values. + * @param sumOfSquaredDeviations the sum of squared deviations of the population values. + * @param bucketOptions the bucket options used to create a histogram for the distribution. + * @param buckets {@link Bucket}s of a histogram. + * @return a {@code Distribution}. + * @since 0.17 + */ + public static Distribution create( + long count, + double sum, + double sumOfSquaredDeviations, + BucketOptions bucketOptions, + List<Bucket> buckets) { + Utils.checkArgument(count >= 0, "count should be non-negative."); + Utils.checkArgument( + sumOfSquaredDeviations >= 0, "sum of squared deviations should be non-negative."); + if (count == 0) { + Utils.checkArgument(sum == 0, "sum should be 0 if count is 0."); + Utils.checkArgument( + sumOfSquaredDeviations == 0, "sum of squared deviations should be 0 if count is 0."); + } + Utils.checkNotNull(bucketOptions, "bucketOptions"); + List<Bucket> bucketsCopy = + Collections.unmodifiableList(new ArrayList<Bucket>(Utils.checkNotNull(buckets, "buckets"))); + Utils.checkListElementNotNull(bucketsCopy, "bucket"); + return new AutoValue_Distribution( + count, sum, sumOfSquaredDeviations, bucketOptions, bucketsCopy); + } + + /** + * Returns the aggregated count. + * + * @return the aggregated count. + * @since 0.17 + */ + public abstract long getCount(); + + /** + * Returns the aggregated sum. + * + * @return the aggregated sum. + * @since 0.17 + */ + public abstract double getSum(); + + /** + * Returns the aggregated sum of squared deviations. + * + * <p>The sum of squared deviations from the mean of the values in the population. For values x_i + * this is: + * + * <p>Sum[i=1..n]((x_i - mean)^2) + * + * <p>If count is zero then this field must be zero. + * + * @return the aggregated sum of squared deviations. + * @since 0.17 + */ + public abstract double getSumOfSquaredDeviations(); + + /** + * Returns bucket options used to create a histogram for the distribution. + * + * @return the {@code BucketOptions} associated with the {@code Distribution}, or {@code null} if + * there isn't one. + * @since 0.17 + */ + @Nullable + public abstract BucketOptions getBucketOptions(); + + /** + * Returns the aggregated histogram {@link Bucket}s. + * + * @return the aggregated histogram buckets. + * @since 0.17 + */ + public abstract List<Bucket> getBuckets(); + + /** + * The bucket options used to create a histogram for the distribution. + * + * @since 0.17 + */ + @Immutable + public abstract static class BucketOptions { + + private BucketOptions() {} + + /** + * Returns a {@link ExplicitOptions}. + * + * <p>The bucket boundaries for that histogram are described by bucket_bounds. This defines + * size(bucket_bounds) + 1 (= N) buckets. The boundaries for bucket index i are: + * + * <ul> + * <li>{@code [0, bucket_bounds[i]) for i == 0} + * <li>{@code [bucket_bounds[i-1], bucket_bounds[i]) for 0 < i < N-1} + * <li>{@code [bucket_bounds[i-1], +infinity) for i == N-1} + * </ul> + * + * <p>If bucket_bounds has no elements (zero size), then there is no histogram associated with + * the Distribution. If bucket_bounds has only one element, there are no finite buckets, and + * that single element is the common boundary of the overflow and underflow buckets. The values + * must be monotonically increasing. + * + * @param bucketBoundaries the bucket boundaries of a distribution (given explicitly). The + * values must be strictly increasing and should be positive values. + * @return a {@code ExplicitOptions} {@code BucketOptions}. + * @since 0.17 + */ + public static BucketOptions explicitOptions(List<Double> bucketBoundaries) { + return ExplicitOptions.create(bucketBoundaries); + } + + /** + * Applies the given match function to the underlying BucketOptions. + * + * @param explicitFunction the function that should be applied if the BucketOptions has type + * {@code ExplicitOptions}. + * @param defaultFunction the function that should be applied if the BucketOptions has a type + * that was added after this {@code match} method was added to the API. See {@link + * io.opencensus.common.Functions} for some common functions for handling unknown types. + * @return the result of the function applied to the underlying BucketOptions. + * @since 0.17 + */ + public abstract <T> T match( + Function<? super ExplicitOptions, T> explicitFunction, + Function<? super BucketOptions, T> defaultFunction); + + /** A Bucket with explicit bounds {@link BucketOptions}. */ + @AutoValue + @Immutable + public abstract static class ExplicitOptions extends BucketOptions { + + ExplicitOptions() {} + + @Override + public final <T> T match( + Function<? super ExplicitOptions, T> explicitFunction, + Function<? super BucketOptions, T> defaultFunction) { + return explicitFunction.apply(this); + } + + /** + * Creates a {@link ExplicitOptions}. + * + * @param bucketBoundaries the bucket boundaries of a distribution (given explicitly). The + * values must be strictly increasing and should be positive. + * @return a {@code ExplicitOptions}. + * @since 0.17 + */ + private static ExplicitOptions create(List<Double> bucketBoundaries) { + Utils.checkNotNull(bucketBoundaries, "bucketBoundaries"); + List<Double> bucketBoundariesCopy = + Collections.unmodifiableList(new ArrayList<Double>(bucketBoundaries)); + checkBucketBoundsAreSorted(bucketBoundariesCopy); + return new AutoValue_Distribution_BucketOptions_ExplicitOptions(bucketBoundariesCopy); + } + + private static void checkBucketBoundsAreSorted(List<Double> bucketBoundaries) { + if (bucketBoundaries.size() >= 1) { + double previous = Utils.checkNotNull(bucketBoundaries.get(0), "bucketBoundary"); + Utils.checkArgument(previous > 0, "bucket boundary should be > 0"); + for (int i = 1; i < bucketBoundaries.size(); i++) { + double next = Utils.checkNotNull(bucketBoundaries.get(i), "bucketBoundary"); + Utils.checkArgument(previous < next, "bucket boundaries not sorted."); + previous = next; + } + } + } + + /** + * Returns the bucket boundaries of this distribution. + * + * @return the bucket boundaries of this distribution. + * @since 0.17 + */ + public abstract List<Double> getBucketBoundaries(); + } + } + + /** + * The histogram bucket of the population values. + * + * @since 0.17 + */ + @AutoValue + @Immutable + public abstract static class Bucket { + + Bucket() {} + + /** + * Creates a {@link Bucket}. + * + * @param count the number of values in each bucket of the histogram. + * @return a {@code Bucket}. + * @since 0.17 + */ + public static Bucket create(long count) { + Utils.checkArgument(count >= 0, "bucket count should be non-negative."); + return new AutoValue_Distribution_Bucket(count, null); + } + + /** + * Creates a {@link Bucket} with an {@link Exemplar}. + * + * @param count the number of values in each bucket of the histogram. + * @param exemplar the {@code Exemplar} of this {@code Bucket}. + * @return a {@code Bucket}. + * @since 0.17 + */ + public static Bucket create(long count, Exemplar exemplar) { + Utils.checkArgument(count >= 0, "bucket count should be non-negative."); + Utils.checkNotNull(exemplar, "exemplar"); + return new AutoValue_Distribution_Bucket(count, exemplar); + } + + /** + * Returns the number of values in each bucket of the histogram. + * + * @return the number of values in each bucket of the histogram. + * @since 0.17 + */ + public abstract long getCount(); + + /** + * Returns the {@link Exemplar} associated with the {@link Bucket}, or {@code null} if there + * isn't one. + * + * @return the {@code Exemplar} associated with the {@code Bucket}, or {@code null} if there + * isn't one. + * @since 0.17 + */ + @Nullable + public abstract Exemplar getExemplar(); + } + + /** + * An example point that may be used to annotate aggregated distribution values, associated with a + * histogram bucket. + * + * @since 0.17 + */ + @Immutable + @AutoValue + public abstract static class Exemplar { + + Exemplar() {} + + /** + * Returns value of the {@link Exemplar} point. + * + * @return value of the {@code Exemplar} point. + * @since 0.17 + */ + public abstract double getValue(); + + /** + * Returns the time that this {@link Exemplar}'s value was recorded. + * + * @return the time that this {@code Exemplar}'s value was recorded. + * @since 0.17 + */ + public abstract Timestamp getTimestamp(); + + /** + * Returns the contextual information about the example value, represented as a string map. + * + * @return the contextual information about the example value. + * @since 0.17 + */ + public abstract Map<String, String> getAttachments(); + + /** + * Creates an {@link Exemplar}. + * + * @param value value of the {@link Exemplar} point. + * @param timestamp the time that this {@code Exemplar}'s value was recorded. + * @param attachments the contextual information about the example value. + * @return an {@code Exemplar}. + * @since 0.17 + */ + public static Exemplar create( + double value, Timestamp timestamp, Map<String, String> attachments) { + Utils.checkNotNull(attachments, "attachments"); + Map<String, String> attachmentsCopy = + Collections.unmodifiableMap(new HashMap<String, String>(attachments)); + for (Entry<String, String> entry : attachmentsCopy.entrySet()) { + Utils.checkNotNull(entry.getKey(), "key of attachments"); + Utils.checkNotNull(entry.getValue(), "value of attachments"); + } + return new AutoValue_Distribution_Exemplar(value, timestamp, attachmentsCopy); + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/export/ExportComponent.java b/api/src/main/java/io/opencensus/metrics/export/ExportComponent.java new file mode 100644 index 00000000..11e1fdbd --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/ExportComponent.java @@ -0,0 +1,60 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import io.opencensus.common.ExperimentalApi; + +/** + * Class that holds the implementation instance for {@link MetricProducerManager}. + * + * <p>Unless otherwise noted all methods (on component) results are cacheable. + * + * @since 0.17 + */ +@ExperimentalApi +public abstract class ExportComponent { + /** + * Returns the no-op implementation of the {@code ExportComponent}. + * + * @return the no-op implementation of the {@code ExportComponent}. + * @since 0.17 + */ + public static ExportComponent newNoopExportComponent() { + return new NoopExportComponent(); + } + + /** + * Returns the global {@link MetricProducerManager} which can be used to register handlers to + * export all the recorded metrics. + * + * @return the implementation of the {@code MetricExporter} or no-op if no implementation linked + * in the binary. + * @since 0.17 + */ + public abstract MetricProducerManager getMetricProducerManager(); + + private static final class NoopExportComponent extends ExportComponent { + + private static final MetricProducerManager METRIC_PRODUCER_MANAGER = + MetricProducerManager.newNoopMetricProducerManager(); + + @Override + public MetricProducerManager getMetricProducerManager() { + return METRIC_PRODUCER_MANAGER; + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/export/Metric.java b/api/src/main/java/io/opencensus/metrics/export/Metric.java new file mode 100644 index 00000000..7b93fc86 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/Metric.java @@ -0,0 +1,137 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.internal.Utils; +import io.opencensus.metrics.export.Value.ValueDistribution; +import io.opencensus.metrics.export.Value.ValueDouble; +import io.opencensus.metrics.export.Value.ValueLong; +import io.opencensus.metrics.export.Value.ValueSummary; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * A {@link Metric} with one or more {@link TimeSeries}. + * + * @since 0.17 + */ +@ExperimentalApi +@Immutable +@AutoValue +public abstract class Metric { + + Metric() {} + + /** + * Creates a {@link Metric}. + * + * @param metricDescriptor the {@link MetricDescriptor}. + * @param timeSeriesList the {@link TimeSeries} list for this metric. + * @return a {@code Metric}. + * @since 0.17 + */ + public static Metric create(MetricDescriptor metricDescriptor, List<TimeSeries> timeSeriesList) { + Utils.checkListElementNotNull( + Utils.checkNotNull(timeSeriesList, "timeSeriesList"), "timeSeries"); + return createInternal( + metricDescriptor, Collections.unmodifiableList(new ArrayList<TimeSeries>(timeSeriesList))); + } + + /** + * Creates a {@link Metric}. + * + * @param metricDescriptor the {@link MetricDescriptor}. + * @param timeSeries the single {@link TimeSeries} for this metric. + * @return a {@code Metric}. + * @since 0.17 + */ + public static Metric createWithOneTimeSeries( + MetricDescriptor metricDescriptor, TimeSeries timeSeries) { + return createInternal( + metricDescriptor, Collections.singletonList(Utils.checkNotNull(timeSeries, "timeSeries"))); + } + + /** + * Creates a {@link Metric}. + * + * @param metricDescriptor the {@link MetricDescriptor}. + * @param timeSeriesList the {@link TimeSeries} list for this metric. + * @return a {@code Metric}. + * @since 0.17 + */ + private static Metric createInternal( + MetricDescriptor metricDescriptor, List<TimeSeries> timeSeriesList) { + Utils.checkNotNull(metricDescriptor, "metricDescriptor"); + checkTypeMatch(metricDescriptor.getType(), timeSeriesList); + return new AutoValue_Metric(metricDescriptor, timeSeriesList); + } + + /** + * Returns the {@link MetricDescriptor} of this metric. + * + * @return the {@code MetricDescriptor} of this metric. + * @since 0.17 + */ + public abstract MetricDescriptor getMetricDescriptor(); + + /** + * Returns the {@link TimeSeries} list for this metric. + * + * <p>The type of the {@link TimeSeries#getPoints()} must match {@link MetricDescriptor.Type}. + * + * @return the {@code TimeSeriesList} for this metric. + * @since 0.17 + */ + public abstract List<TimeSeries> getTimeSeriesList(); + + private static void checkTypeMatch(MetricDescriptor.Type type, List<TimeSeries> timeSeriesList) { + for (TimeSeries timeSeries : timeSeriesList) { + for (Point point : timeSeries.getPoints()) { + Value value = point.getValue(); + String valueClassName = ""; + if (value.getClass().getSuperclass() != null) { // work around nullness check + // AutoValue classes should always have a super class. + valueClassName = value.getClass().getSuperclass().getSimpleName(); + } + switch (type) { + case GAUGE_INT64: + case CUMULATIVE_INT64: + Utils.checkArgument( + value instanceof ValueLong, "Type mismatch: %s, %s.", type, valueClassName); + break; + case CUMULATIVE_DOUBLE: + case GAUGE_DOUBLE: + Utils.checkArgument( + value instanceof ValueDouble, "Type mismatch: %s, %s.", type, valueClassName); + break; + case GAUGE_DISTRIBUTION: + case CUMULATIVE_DISTRIBUTION: + Utils.checkArgument( + value instanceof ValueDistribution, "Type mismatch: %s, %s.", type, valueClassName); + break; + case SUMMARY: + Utils.checkArgument( + value instanceof ValueSummary, "Type mismatch: %s, %s.", type, valueClassName); + } + } + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/export/MetricDescriptor.java b/api/src/main/java/io/opencensus/metrics/export/MetricDescriptor.java new file mode 100644 index 00000000..a4629f8e --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/MetricDescriptor.java @@ -0,0 +1,173 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.internal.Utils; +import io.opencensus.metrics.LabelKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * {@link MetricDescriptor} defines a {@code Metric} type and its schema. + * + * @since 0.17 + */ +@ExperimentalApi +@Immutable +@AutoValue +public abstract class MetricDescriptor { + + MetricDescriptor() {} + + /** + * Creates a {@link MetricDescriptor}. + * + * @param name name of {@code MetricDescriptor}. + * @param description description of {@code MetricDescriptor}. + * @param unit the metric unit. + * @param type type of {@code MetricDescriptor}. + * @param labelKeys the label keys associated with the {@code MetricDescriptor}. + * @return a {@code MetricDescriptor}. + * @since 0.17 + */ + public static MetricDescriptor create( + String name, String description, String unit, Type type, List<LabelKey> labelKeys) { + Utils.checkNotNull(labelKeys, "labelKeys"); + Utils.checkListElementNotNull(labelKeys, "labelKey"); + return new AutoValue_MetricDescriptor( + name, + description, + unit, + type, + Collections.unmodifiableList(new ArrayList<LabelKey>(labelKeys))); + } + + /** + * Returns the metric descriptor name. + * + * @return the metric descriptor name. + * @since 0.17 + */ + public abstract String getName(); + + /** + * Returns the description of this metric descriptor. + * + * @return the description of this metric descriptor. + * @since 0.17 + */ + public abstract String getDescription(); + + /** + * Returns the unit of this metric descriptor. + * + * @return the unit of this metric descriptor. + * @since 0.17 + */ + public abstract String getUnit(); + + /** + * Returns the type of this metric descriptor. + * + * @return the type of this metric descriptor. + * @since 0.17 + */ + public abstract Type getType(); + + /** + * Returns the label keys associated with this metric descriptor. + * + * @return the label keys associated with this metric descriptor. + * @since 0.17 + */ + public abstract List<LabelKey> getLabelKeys(); + + /** + * The kind of metric. It describes how the data is reported. + * + * <p>A gauge is an instantaneous measurement of a value. + * + * <p>A cumulative measurement is a value accumulated over a time interval. In a time series, + * cumulative measurements should have the same start time and increasing end times, until an + * event resets the cumulative value to zero and sets a new start time for the following points. + * + * @since 0.17 + */ + public enum Type { + + /** + * An instantaneous measurement of an int64 value. + * + * @since 0.17 + */ + GAUGE_INT64, + + /** + * An instantaneous measurement of a double value. + * + * @since 0.17 + */ + GAUGE_DOUBLE, + + /** + * An instantaneous measurement of a distribution value. The count and sum can go both up and + * down. Used in scenarios like a snapshot of time the current items in a queue have spent + * there. + * + * @since 0.17 + */ + GAUGE_DISTRIBUTION, + + /** + * An cumulative measurement of an int64 value. + * + * @since 0.17 + */ + CUMULATIVE_INT64, + + /** + * An cumulative measurement of a double value. + * + * @since 0.17 + */ + CUMULATIVE_DOUBLE, + + /** + * An cumulative measurement of a distribution value. The count and sum can only go up, if + * resets then the start_time should also be reset. + * + * @since 0.17 + */ + CUMULATIVE_DISTRIBUTION, + + /** + * Some frameworks implemented DISTRIBUTION as a summary of observations (usually things like + * request durations and response sizes). While it also provides a total count of observations + * and a sum of all observed values, it calculates configurable quantiles over a sliding time + * window. + * + * <p>This is not recommended, since it cannot be aggregated. + * + * @since 0.17 + */ + SUMMARY, + } +} diff --git a/api/src/main/java/io/opencensus/metrics/export/MetricProducer.java b/api/src/main/java/io/opencensus/metrics/export/MetricProducer.java new file mode 100644 index 00000000..739a0a9f --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/MetricProducer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import io.opencensus.common.ExperimentalApi; +import java.util.Collection; + +/** + * A {@link Metric} producer that can be registered for exporting using {@link + * MetricProducerManager}. + * + * <p>All implementation MUST be thread-safe. + * + * @since 0.17 + */ +@ExperimentalApi +public abstract class MetricProducer { + + /** + * Returns a collection of produced {@link Metric}s to be exported. + * + * @return a collection of produced {@link Metric}s to be exported. + * @since 0.17 + */ + public abstract Collection<Metric> getMetrics(); +} diff --git a/api/src/main/java/io/opencensus/metrics/export/MetricProducerManager.java b/api/src/main/java/io/opencensus/metrics/export/MetricProducerManager.java new file mode 100644 index 00000000..304d9294 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/MetricProducerManager.java @@ -0,0 +1,88 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import io.opencensus.common.ExperimentalApi; +import io.opencensus.internal.Utils; +import java.util.Collections; +import java.util.Set; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Keeps a set of {@link MetricProducer} that is used by exporters to determine the metrics that + * need to be exported. + * + * @since 0.17 + */ +@ExperimentalApi +@ThreadSafe +public abstract class MetricProducerManager { + + /** + * Adds the {@link MetricProducer} to the manager if it is not already present. + * + * @param metricProducer the {@code MetricProducer} to be added to the manager. + * @since 0.17 + */ + public abstract void add(MetricProducer metricProducer); + + /** + * Removes the {@link MetricProducer} to the manager if it is present. + * + * @param metricProducer the {@code MetricProducer} to be removed from the manager. + * @since 0.17 + */ + public abstract void remove(MetricProducer metricProducer); + + /** + * Returns all registered {@link MetricProducer}s that should be exported. + * + * <p>This method should be used by any metrics exporter that automatically exports data for + * {@code MetricProducer} registered with the {@code MetricProducerManager}. + * + * @return all registered {@code MetricProducer}s that should be exported. + * @since 0.17 + */ + public abstract Set<MetricProducer> getAllMetricProducer(); + + /** + * Returns a no-op implementation for {@link MetricProducerManager}. + * + * @return a no-op implementation for {@code MetricProducerManager}. + */ + static MetricProducerManager newNoopMetricProducerManager() { + return new NoopMetricProducerManager(); + } + + private static final class NoopMetricProducerManager extends MetricProducerManager { + + @Override + public void add(MetricProducer metricProducer) { + Utils.checkNotNull(metricProducer, "metricProducer"); + } + + @Override + public void remove(MetricProducer metricProducer) { + Utils.checkNotNull(metricProducer, "metricProducer"); + } + + @Override + public Set<MetricProducer> getAllMetricProducer() { + return Collections.emptySet(); + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/export/Point.java b/api/src/main/java/io/opencensus/metrics/export/Point.java new file mode 100644 index 00000000..1f382f9b --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/Point.java @@ -0,0 +1,63 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.common.Timestamp; +import javax.annotation.concurrent.Immutable; + +/** + * A timestamped measurement of a {@code TimeSeries}. + * + * @since 0.17 + */ +@ExperimentalApi +@AutoValue +@Immutable +public abstract class Point { + + Point() {} + + /** + * Creates a {@link Point}. + * + * @param value the {@link Value} of this {@link Point}. + * @param timestamp the {@link Timestamp} when this {@link Point} was recorded. + * @return a {@code Point}. + * @since 0.17 + */ + public static Point create(Value value, Timestamp timestamp) { + return new AutoValue_Point(value, timestamp); + } + + /** + * Returns the {@link Value}. + * + * @return the {@code Value}. + * @since 0.17 + */ + public abstract Value getValue(); + + /** + * Returns the {@link Timestamp} when this {@link Point} was recorded. + * + * @return the {@code Timestamp}. + * @since 0.17 + */ + public abstract Timestamp getTimestamp(); +} diff --git a/api/src/main/java/io/opencensus/metrics/export/Summary.java b/api/src/main/java/io/opencensus/metrics/export/Summary.java new file mode 100644 index 00000000..c82ca961 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/Summary.java @@ -0,0 +1,187 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.internal.Utils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * Implementation of the {@link Distribution} as a summary of observations. + * + * <p>This is not recommended, since it cannot be aggregated. + * + * @since 0.17 + */ +@ExperimentalApi +@AutoValue +@Immutable +public abstract class Summary { + Summary() {} + + /** + * Creates a {@link Summary}. + * + * @param count the count of the population values. + * @param sum the sum of the population values. + * @param snapshot bucket boundaries of a histogram. + * @return a {@code Summary} with the given values. + * @since 0.17 + */ + public static Summary create(@Nullable Long count, @Nullable Double sum, Snapshot snapshot) { + checkCountAndSum(count, sum); + Utils.checkNotNull(snapshot, "snapshot"); + return new AutoValue_Summary(count, sum, snapshot); + } + + /** + * Returns the aggregated count. If not available returns {@code null}. + * + * @return the aggregated count. + * @since 0.17 + */ + @Nullable + public abstract Long getCount(); + + /** + * Returns the aggregated sum. If not available returns {@code null}. + * + * @return the aggregated sum. + * @since 0.17 + */ + @Nullable + public abstract Double getSum(); + + /** + * Returns the {@link Snapshot}. + * + * @return the {@code Snapshot}. + * @since 0.17 + */ + public abstract Snapshot getSnapshot(); + + /** + * Represents the summary observation of the recorded events over a sliding time window. + * + * @since 0.17 + */ + @Immutable + @AutoValue + public abstract static class Snapshot { + /** + * Returns the number of values in this {@code Snapshot}. If not available returns {@code null}. + * + * @return the number of values in this {@code Snapshot}. + * @since 0.17 + */ + @Nullable + public abstract Long getCount(); + + /** + * Returns the sum of values in this {@code Snapshot}. If not available returns {@code null}. + * + * @return the sum of values in this {@code Snapshot}. + * @since 0.17 + */ + @Nullable + public abstract Double getSum(); + + /** + * Returns the list of {@code ValueAtPercentile}s in this {@code Snapshot}. + * + * @return the list of {@code ValueAtPercentile}s in this {@code Snapshot}. + * @since 0.17 + */ + public abstract List<ValueAtPercentile> getValueAtPercentiles(); + + /** + * Creates a {@link Snapshot}. + * + * @param count the number of values in this {@code Snapshot}. + * @param sum the number of values in this {@code Snapshot}. + * @param valueAtPercentiles the list of {@code ValueAtPercentile}. + * @return a {@code Snapshot} with the given values. + * @since 0.17 + */ + public static Snapshot create( + @Nullable Long count, @Nullable Double sum, List<ValueAtPercentile> valueAtPercentiles) { + checkCountAndSum(count, sum); + Utils.checkNotNull(valueAtPercentiles, "valueAtPercentiles"); + Utils.checkListElementNotNull(valueAtPercentiles, "value in valueAtPercentiles"); + return new AutoValue_Summary_Snapshot( + count, + sum, + Collections.unmodifiableList(new ArrayList<ValueAtPercentile>(valueAtPercentiles))); + } + + /** + * Represents the value at a given percentile of a distribution. + * + * @since 0.17 + */ + @Immutable + @AutoValue + public abstract static class ValueAtPercentile { + /** + * Returns the percentile in this {@code ValueAtPercentile}. + * + * <p>Must be in the interval (0.0, 100.0]. + * + * @return the percentile in this {@code ValueAtPercentile}. + * @since 0.17 + */ + public abstract double getPercentile(); + + /** + * Returns the value in this {@code ValueAtPercentile}. + * + * @return the value in this {@code ValueAtPercentile}. + * @since 0.17 + */ + public abstract double getValue(); + + /** + * Creates a {@link ValueAtPercentile}. + * + * @param percentile the percentile in this {@code ValueAtPercentile}. + * @param value the value in this {@code ValueAtPercentile}. + * @return a {@code ValueAtPercentile} with the given values. + * @since 0.17 + */ + public static ValueAtPercentile create(double percentile, double value) { + Utils.checkArgument( + 0 < percentile && percentile <= 100.0, + "percentile must be in the interval (0.0, 100.0]"); + Utils.checkArgument(value >= 0, "value must be non-negative"); + return new AutoValue_Summary_Snapshot_ValueAtPercentile(percentile, value); + } + } + } + + private static void checkCountAndSum(@Nullable Long count, @Nullable Double sum) { + Utils.checkArgument(count == null || count >= 0, "count must be non-negative."); + Utils.checkArgument(sum == null || sum >= 0, "sum must be non-negative."); + if (count != null && count == 0) { + Utils.checkArgument(sum == null || sum == 0, "sum must be 0 if count is 0."); + } + } +} diff --git a/api/src/main/java/io/opencensus/metrics/export/TimeSeries.java b/api/src/main/java/io/opencensus/metrics/export/TimeSeries.java new file mode 100644 index 00000000..bfaeae98 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/TimeSeries.java @@ -0,0 +1,127 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.common.Timestamp; +import io.opencensus.internal.Utils; +import io.opencensus.metrics.LabelKey; +import io.opencensus.metrics.LabelValue; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A collection of data points that describes the time-varying values of a {@code Metric}. + * + * @since 0.17 + */ +@ExperimentalApi +@Immutable +@AutoValue +public abstract class TimeSeries { + + TimeSeries() {} + + /** + * Creates a {@link TimeSeries}. + * + * @param labelValues the {@code LabelValue}s that uniquely identify this {@code TimeSeries}. + * @param points the data {@code Point}s of this {@code TimeSeries}. + * @param startTimestamp the start {@code Timestamp} of this {@code TimeSeries}. Must be non-null + * for cumulative {@code Point}s. + * @return a {@code TimeSeries}. + * @since 0.17 + */ + public static TimeSeries create( + List<LabelValue> labelValues, List<Point> points, @Nullable Timestamp startTimestamp) { + Utils.checkNotNull(points, "points"); + Utils.checkListElementNotNull(points, "point"); + return createInternal( + labelValues, Collections.unmodifiableList(new ArrayList<Point>(points)), startTimestamp); + } + + /** + * Creates a {@link TimeSeries}. + * + * @param labelValues the {@code LabelValue}s that uniquely identify this {@code TimeSeries}. + * @param point the single data {@code Point} of this {@code TimeSeries}. + * @param startTimestamp the start {@code Timestamp} of this {@code TimeSeries}. Must be non-null + * for cumulative {@code Point}s. + * @return a {@code TimeSeries}. + * @since 0.17 + */ + public static TimeSeries createWithOnePoint( + List<LabelValue> labelValues, Point point, @Nullable Timestamp startTimestamp) { + Utils.checkNotNull(point, "point"); + return createInternal(labelValues, Collections.singletonList(point), startTimestamp); + } + + /** + * Creates a {@link TimeSeries}. + * + * @param labelValues the {@code LabelValue}s that uniquely identify this {@code TimeSeries}. + * @param points the data {@code Point}s of this {@code TimeSeries}. + * @param startTimestamp the start {@code Timestamp} of this {@code TimeSeries}. Must be non-null + * for cumulative {@code Point}s. + * @return a {@code TimeSeries}. + */ + private static TimeSeries createInternal( + List<LabelValue> labelValues, List<Point> points, @Nullable Timestamp startTimestamp) { + // Fail fast on null lists to prevent NullPointerException when copying the lists. + Utils.checkNotNull(labelValues, "labelValues"); + Utils.checkListElementNotNull(labelValues, "labelValue"); + return new AutoValue_TimeSeries( + Collections.unmodifiableList(new ArrayList<LabelValue>(labelValues)), + points, + startTimestamp); + } + + /** + * Returns the set of {@link LabelValue}s that uniquely identify this {@link TimeSeries}. + * + * <p>Apply to all {@link Point}s. + * + * <p>The order of {@link LabelValue}s must match that of {@link LabelKey}s in the {@code + * MetricDescriptor}. + * + * @return the {@code LabelValue}s. + * @since 0.17 + */ + public abstract List<LabelValue> getLabelValues(); + + /** + * Returns the data {@link Point}s of this {@link TimeSeries}. + * + * @return the data {@code Point}s. + * @since 0.17 + */ + public abstract List<Point> getPoints(); + + /** + * Returns the start {@link Timestamp} of this {@link TimeSeries} if the {@link Point}s are + * cumulative, or {@code null} if the {@link Point}s are gauge. + * + * @return the start {@code Timestamp} or {@code null}. + * @since 0.17 + */ + @Nullable + public abstract Timestamp getStartTimestamp(); +} diff --git a/api/src/main/java/io/opencensus/metrics/export/Value.java b/api/src/main/java/io/opencensus/metrics/export/Value.java new file mode 100644 index 00000000..00a939c0 --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/export/Value.java @@ -0,0 +1,246 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.metrics.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.common.Function; +import javax.annotation.concurrent.Immutable; + +/** + * The actual point value for a {@link Point}. + * + * <p>Currently there are three types of {@link Value}: + * + * <ul> + * <li>{@code double} + * <li>{@code long} + * <li>{@link Distribution} + * </ul> + * + * <p>Each {@link Point} contains exactly one of the three {@link Value} types. + * + * @since 0.17 + */ +@ExperimentalApi +@Immutable +public abstract class Value { + + Value() {} + + /** + * Returns a double {@link Value}. + * + * @param value value in double. + * @return a double {@code Value}. + * @since 0.17 + */ + public static Value doubleValue(double value) { + return ValueDouble.create(value); + } + + /** + * Returns a long {@link Value}. + * + * @param value value in long. + * @return a long {@code Value}. + * @since 0.17 + */ + public static Value longValue(long value) { + return ValueLong.create(value); + } + + /** + * Returns a {@link Distribution} {@link Value}. + * + * @param value value in {@link Distribution}. + * @return a {@code Distribution} {@code Value}. + * @since 0.17 + */ + public static Value distributionValue(Distribution value) { + return ValueDistribution.create(value); + } + + /** + * Returns a {@link Summary} {@link Value}. + * + * @param value value in {@link Summary}. + * @return a {@code Summary} {@code Value}. + * @since 0.17 + */ + public static Value summaryValue(Summary value) { + return ValueSummary.create(value); + } + + /** + * Applies the given match function to the underlying data type. + * + * @since 0.17 + */ + public abstract <T> T match( + Function<? super Double, T> doubleFunction, + Function<? super Long, T> longFunction, + Function<? super Distribution, T> distributionFunction, + Function<? super Summary, T> summaryFunction, + Function<? super Value, T> defaultFunction); + + /** A 64-bit double-precision floating-point {@link Value}. */ + @AutoValue + @Immutable + abstract static class ValueDouble extends Value { + + ValueDouble() {} + + @Override + public final <T> T match( + Function<? super Double, T> doubleFunction, + Function<? super Long, T> longFunction, + Function<? super Distribution, T> distributionFunction, + Function<? super Summary, T> summaryFunction, + Function<? super Value, T> defaultFunction) { + return doubleFunction.apply(getValue()); + } + + /** + * Creates a {@link ValueDouble}. + * + * @param value the value in double. + * @return a {@code ValueDouble}. + */ + static ValueDouble create(double value) { + return new AutoValue_Value_ValueDouble(value); + } + + /** + * Returns the double value. + * + * @return the double value. + */ + abstract double getValue(); + } + + /** A 64-bit integer {@link Value}. */ + @AutoValue + @Immutable + abstract static class ValueLong extends Value { + + ValueLong() {} + + @Override + public final <T> T match( + Function<? super Double, T> doubleFunction, + Function<? super Long, T> longFunction, + Function<? super Distribution, T> distributionFunction, + Function<? super Summary, T> summaryFunction, + Function<? super Value, T> defaultFunction) { + return longFunction.apply(getValue()); + } + + /** + * Creates a {@link ValueLong}. + * + * @param value the value in long. + * @return a {@code ValueLong}. + */ + static ValueLong create(long value) { + return new AutoValue_Value_ValueLong(value); + } + + /** + * Returns the long value. + * + * @return the long value. + */ + abstract long getValue(); + } + + /** + * {@link ValueDistribution} contains summary statistics for a population of values. It optionally + * contains a histogram representing the distribution of those values across a set of buckets. + */ + @AutoValue + @Immutable + abstract static class ValueDistribution extends Value { + + ValueDistribution() {} + + @Override + public final <T> T match( + Function<? super Double, T> doubleFunction, + Function<? super Long, T> longFunction, + Function<? super Distribution, T> distributionFunction, + Function<? super Summary, T> summaryFunction, + Function<? super Value, T> defaultFunction) { + return distributionFunction.apply(getValue()); + } + + /** + * Creates a {@link ValueDistribution}. + * + * @param value the {@link Distribution} value. + * @return a {@code ValueDistribution}. + */ + static ValueDistribution create(Distribution value) { + return new AutoValue_Value_ValueDistribution(value); + } + + /** + * Returns the {@link Distribution} value. + * + * @return the {@code Distribution} value. + */ + abstract Distribution getValue(); + } + + /** + * {@link ValueSummary} contains a snapshot representing values calculated over an arbitrary time + * window. + */ + @AutoValue + @Immutable + abstract static class ValueSummary extends Value { + + ValueSummary() {} + + @Override + public final <T> T match( + Function<? super Double, T> doubleFunction, + Function<? super Long, T> longFunction, + Function<? super Distribution, T> distributionFunction, + Function<? super Summary, T> summaryFunction, + Function<? super Value, T> defaultFunction) { + return summaryFunction.apply(getValue()); + } + + /** + * Creates a {@link ValueSummary}. + * + * @param value the {@link Summary} value. + * @return a {@code ValueSummary}. + */ + static ValueSummary create(Summary value) { + return new AutoValue_Value_ValueSummary(value); + } + + /** + * Returns the {@link Summary} value. + * + * @return the {@code Summary} value. + */ + abstract Summary getValue(); + } +} diff --git a/api/src/main/java/io/opencensus/metrics/package-info.java b/api/src/main/java/io/opencensus/metrics/package-info.java new file mode 100644 index 00000000..33eadf0c --- /dev/null +++ b/api/src/main/java/io/opencensus/metrics/package-info.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This package describes the Metrics data model. Metrics are a data model for what stats exporters + * take as input. This data model may eventually become the wire format for metrics. + * + * <p>WARNING: Currently all the public classes under this package are marked as {@link + * io.opencensus.common.ExperimentalApi}. The classes and APIs under {@link io.opencensus.metrics} + * are likely to get backwards-incompatible updates in the future. DO NOT USE except for + * experimental purposes. + * + * <p>Please see + * https://github.com/census-instrumentation/opencensus-specs/blob/master/stats/Metrics.md and + * https://github.com/census-instrumentation/opencensus-proto/blob/master/opencensus/proto/stats/metrics/metrics.proto + * for more details. + */ +@io.opencensus.common.ExperimentalApi +package io.opencensus.metrics; diff --git a/api/src/main/java/io/opencensus/stats/Aggregation.java b/api/src/main/java/io/opencensus/stats/Aggregation.java new file mode 100644 index 00000000..9c95e847 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/Aggregation.java @@ -0,0 +1,239 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Function; +import io.opencensus.internal.Utils; +import javax.annotation.concurrent.Immutable; + +/** + * {@link Aggregation} is the process of combining a certain set of {@code MeasureValue}s for a + * given {@code Measure} into an {@link AggregationData}. + * + * <p>{@link Aggregation} currently supports 4 types of basic aggregation: + * + * <ul> + * <li>Sum + * <li>Count + * <li>Distribution + * <li>LastValue + * </ul> + * + * <p>When creating a {@link View}, one {@link Aggregation} needs to be specified as how to + * aggregate {@code MeasureValue}s. + * + * @since 0.8 + */ +@Immutable +public abstract class Aggregation { + + private Aggregation() {} + + /** + * Applies the given match function to the underlying data type. + * + * @since 0.13 + */ + public abstract <T> T match( + Function<? super Sum, T> p0, + Function<? super Count, T> p1, + Function<? super Distribution, T> p2, + Function<? super LastValue, T> p3, + Function<? super Aggregation, T> defaultFunction); + + /** + * Calculate sum on aggregated {@code MeasureValue}s. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class Sum extends Aggregation { + + Sum() {} + + private static final Sum INSTANCE = new AutoValue_Aggregation_Sum(); + + /** + * Construct a {@code Sum}. + * + * @return a new {@code Sum}. + * @since 0.8 + */ + public static Sum create() { + return INSTANCE; + } + + @Override + public final <T> T match( + Function<? super Sum, T> p0, + Function<? super Count, T> p1, + Function<? super Distribution, T> p2, + Function<? super LastValue, T> p3, + Function<? super Aggregation, T> defaultFunction) { + return p0.apply(this); + } + } + + /** + * Calculate count on aggregated {@code MeasureValue}s. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class Count extends Aggregation { + + Count() {} + + private static final Count INSTANCE = new AutoValue_Aggregation_Count(); + + /** + * Construct a {@code Count}. + * + * @return a new {@code Count}. + * @since 0.8 + */ + public static Count create() { + return INSTANCE; + } + + @Override + public final <T> T match( + Function<? super Sum, T> p0, + Function<? super Count, T> p1, + Function<? super Distribution, T> p2, + Function<? super LastValue, T> p3, + Function<? super Aggregation, T> defaultFunction) { + return p1.apply(this); + } + } + + /** + * Calculate mean on aggregated {@code MeasureValue}s. + * + * @since 0.8 + * @deprecated since 0.13, use {@link Distribution} instead. + */ + @Immutable + @AutoValue + @Deprecated + @AutoValue.CopyAnnotations + public abstract static class Mean extends Aggregation { + + Mean() {} + + private static final Mean INSTANCE = new AutoValue_Aggregation_Mean(); + + /** + * Construct a {@code Mean}. + * + * @return a new {@code Mean}. + * @since 0.8 + */ + public static Mean create() { + return INSTANCE; + } + + @Override + public final <T> T match( + Function<? super Sum, T> p0, + Function<? super Count, T> p1, + Function<? super Distribution, T> p2, + Function<? super LastValue, T> p3, + Function<? super Aggregation, T> defaultFunction) { + return defaultFunction.apply(this); + } + } + + /** + * Calculate distribution stats on aggregated {@code MeasureValue}s. Distribution includes mean, + * count, histogram, min, max and sum of squared deviations. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class Distribution extends Aggregation { + + Distribution() {} + + /** + * Construct a {@code Distribution}. + * + * @return a new {@code Distribution}. + * @since 0.8 + */ + public static Distribution create(BucketBoundaries bucketBoundaries) { + Utils.checkNotNull(bucketBoundaries, "bucketBoundaries"); + return new AutoValue_Aggregation_Distribution(bucketBoundaries); + } + + /** + * Returns the {@code Distribution}'s bucket boundaries. + * + * @return the {@code Distribution}'s bucket boundaries. + * @since 0.8 + */ + public abstract BucketBoundaries getBucketBoundaries(); + + @Override + public final <T> T match( + Function<? super Sum, T> p0, + Function<? super Count, T> p1, + Function<? super Distribution, T> p2, + Function<? super LastValue, T> p3, + Function<? super Aggregation, T> defaultFunction) { + return p2.apply(this); + } + } + + /** + * Calculate the last value of aggregated {@code MeasureValue}s. + * + * @since 0.13 + */ + @Immutable + @AutoValue + public abstract static class LastValue extends Aggregation { + + LastValue() {} + + private static final LastValue INSTANCE = new AutoValue_Aggregation_LastValue(); + + /** + * Construct a {@code LastValue}. + * + * @return a new {@code LastValue}. + * @since 0.13 + */ + public static LastValue create() { + return INSTANCE; + } + + @Override + public final <T> T match( + Function<? super Sum, T> p0, + Function<? super Count, T> p1, + Function<? super Distribution, T> p2, + Function<? super LastValue, T> p3, + Function<? super Aggregation, T> defaultFunction) { + return p3.apply(this); + } + } +} diff --git a/api/src/main/java/io/opencensus/stats/AggregationData.java b/api/src/main/java/io/opencensus/stats/AggregationData.java new file mode 100644 index 00000000..c6e12b67 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/AggregationData.java @@ -0,0 +1,555 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Function; +import io.opencensus.common.Timestamp; +import io.opencensus.internal.Utils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.concurrent.Immutable; + +/** + * {@link AggregationData} is the result of applying a given {@link Aggregation} to a set of {@code + * MeasureValue}s. + * + * <p>{@link AggregationData} currently supports 6 types of basic aggregation values: + * + * <ul> + * <li>SumDataDouble + * <li>SumDataLong + * <li>CountData + * <li>DistributionData + * <li>LastValueDataDouble + * <li>LastValueDataLong + * </ul> + * + * <p>{@link ViewData} will contain one {@link AggregationData}, corresponding to its {@link + * Aggregation} definition in {@link View}. + * + * @since 0.8 + */ +@Immutable +public abstract class AggregationData { + + private AggregationData() {} + + /** + * Applies the given match function to the underlying data type. + * + * @since 0.13 + */ + public abstract <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction); + + /** + * The sum value of aggregated {@code MeasureValueDouble}s. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class SumDataDouble extends AggregationData { + + SumDataDouble() {} + + /** + * Creates a {@code SumDataDouble}. + * + * @param sum the aggregated sum. + * @return a {@code SumDataDouble}. + * @since 0.8 + */ + public static SumDataDouble create(double sum) { + return new AutoValue_AggregationData_SumDataDouble(sum); + } + + /** + * Returns the aggregated sum. + * + * @return the aggregated sum. + * @since 0.8 + */ + public abstract double getSum(); + + @Override + public final <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction) { + return p0.apply(this); + } + } + + /** + * The sum value of aggregated {@code MeasureValueLong}s. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class SumDataLong extends AggregationData { + + SumDataLong() {} + + /** + * Creates a {@code SumDataLong}. + * + * @param sum the aggregated sum. + * @return a {@code SumDataLong}. + * @since 0.8 + */ + public static SumDataLong create(long sum) { + return new AutoValue_AggregationData_SumDataLong(sum); + } + + /** + * Returns the aggregated sum. + * + * @return the aggregated sum. + * @since 0.8 + */ + public abstract long getSum(); + + @Override + public final <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction) { + return p1.apply(this); + } + } + + /** + * The count value of aggregated {@code MeasureValue}s. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class CountData extends AggregationData { + + CountData() {} + + /** + * Creates a {@code CountData}. + * + * @param count the aggregated count. + * @return a {@code CountData}. + * @since 0.8 + */ + public static CountData create(long count) { + return new AutoValue_AggregationData_CountData(count); + } + + /** + * Returns the aggregated count. + * + * @return the aggregated count. + * @since 0.8 + */ + public abstract long getCount(); + + @Override + public final <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction) { + return p2.apply(this); + } + } + + /** + * The mean value of aggregated {@code MeasureValue}s. + * + * @since 0.8 + * @deprecated since 0.13, use {@link DistributionData} instead. + */ + @Immutable + @AutoValue + @Deprecated + @AutoValue.CopyAnnotations + public abstract static class MeanData extends AggregationData { + + MeanData() {} + + /** + * Creates a {@code MeanData}. + * + * @param mean the aggregated mean. + * @param count the aggregated count. + * @return a {@code MeanData}. + * @since 0.8 + */ + public static MeanData create(double mean, long count) { + return new AutoValue_AggregationData_MeanData(mean, count); + } + + /** + * Returns the aggregated mean. + * + * @return the aggregated mean. + * @since 0.8 + */ + public abstract double getMean(); + + /** + * Returns the aggregated count. + * + * @return the aggregated count. + * @since 0.8 + */ + public abstract long getCount(); + + @Override + public final <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction) { + return defaultFunction.apply(this); + } + } + + /** + * The distribution stats of aggregated {@code MeasureValue}s. Distribution stats include mean, + * count, histogram, min, max and sum of squared deviations. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class DistributionData extends AggregationData { + + DistributionData() {} + + /** + * Creates a {@code DistributionData}. + * + * @param mean mean value. + * @param count count value. + * @param min min value. + * @param max max value. + * @param sumOfSquaredDeviations sum of squared deviations. + * @param bucketCounts histogram bucket counts. + * @param exemplars the exemplars associated with histogram buckets. + * @return a {@code DistributionData}. + * @since 0.16 + */ + public static DistributionData create( + double mean, + long count, + double min, + double max, + double sumOfSquaredDeviations, + List<Long> bucketCounts, + List<Exemplar> exemplars) { + if (min != Double.POSITIVE_INFINITY || max != Double.NEGATIVE_INFINITY) { + Utils.checkArgument(min <= max, "max should be greater or equal to min."); + } + + Utils.checkNotNull(bucketCounts, "bucketCounts"); + List<Long> bucketCountsCopy = Collections.unmodifiableList(new ArrayList<Long>(bucketCounts)); + for (Long bucket : bucketCountsCopy) { + Utils.checkNotNull(bucket, "bucket"); + } + + Utils.checkNotNull(exemplars, "exemplar list should not be null."); + for (Exemplar exemplar : exemplars) { + Utils.checkNotNull(exemplar, "exemplar"); + } + + return new AutoValue_AggregationData_DistributionData( + mean, + count, + min, + max, + sumOfSquaredDeviations, + bucketCountsCopy, + Collections.<Exemplar>unmodifiableList(new ArrayList<Exemplar>(exemplars))); + } + + /** + * Creates a {@code DistributionData}. + * + * @param mean mean value. + * @param count count value. + * @param min min value. + * @param max max value. + * @param sumOfSquaredDeviations sum of squared deviations. + * @param bucketCounts histogram bucket counts. + * @return a {@code DistributionData}. + * @since 0.8 + */ + public static DistributionData create( + double mean, + long count, + double min, + double max, + double sumOfSquaredDeviations, + List<Long> bucketCounts) { + return create( + mean, + count, + min, + max, + sumOfSquaredDeviations, + bucketCounts, + Collections.<Exemplar>emptyList()); + } + + /** + * Returns the aggregated mean. + * + * @return the aggregated mean. + * @since 0.8 + */ + public abstract double getMean(); + + /** + * Returns the aggregated count. + * + * @return the aggregated count. + * @since 0.8 + */ + public abstract long getCount(); + + /** + * Returns the minimum of the population values. + * + * @return the minimum of the population values. + * @since 0.8 + */ + public abstract double getMin(); + + /** + * Returns the maximum of the population values. + * + * @return the maximum of the population values. + * @since 0.8 + */ + public abstract double getMax(); + + /** + * Returns the aggregated sum of squared deviations. + * + * @return the aggregated sum of squared deviations. + * @since 0.8 + */ + public abstract double getSumOfSquaredDeviations(); + + /** + * Returns the aggregated bucket counts. The returned list is immutable, trying to update it + * will throw an {@code UnsupportedOperationException}. + * + * @return the aggregated bucket counts. + * @since 0.8 + */ + public abstract List<Long> getBucketCounts(); + + /** + * Returns the {@link Exemplar}s associated with histogram buckets. + * + * @return the {@code Exemplar}s associated with histogram buckets. + * @since 0.16 + */ + public abstract List<Exemplar> getExemplars(); + + @Override + public final <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction) { + return p3.apply(this); + } + + /** + * An example point that may be used to annotate aggregated distribution values, associated with + * a histogram bucket. + * + * @since 0.16 + */ + @Immutable + @AutoValue + public abstract static class Exemplar { + + Exemplar() {} + + /** + * Returns value of the {@link Exemplar} point. + * + * @return value of the {@code Exemplar} point. + * @since 0.16 + */ + public abstract double getValue(); + + /** + * Returns the time that this {@link Exemplar}'s value was recorded. + * + * @return the time that this {@code Exemplar}'s value was recorded. + * @since 0.16 + */ + public abstract Timestamp getTimestamp(); + + /** + * Returns the contextual information about the example value, represented as a string map. + * + * @return the contextual information about the example value. + * @since 0.16 + */ + public abstract Map<String, String> getAttachments(); + + /** + * Creates an {@link Exemplar}. + * + * @param value value of the {@link Exemplar} point. + * @param timestamp the time that this {@code Exemplar}'s value was recorded. + * @param attachments the contextual information about the example value. + * @return an {@code Exemplar}. + * @since 0.16 + */ + public static Exemplar create( + double value, Timestamp timestamp, Map<String, String> attachments) { + Utils.checkNotNull(attachments, "attachments"); + Map<String, String> attachmentsCopy = + Collections.unmodifiableMap(new HashMap<String, String>(attachments)); + for (Entry<String, String> entry : attachmentsCopy.entrySet()) { + Utils.checkNotNull(entry.getKey(), "key of attachments"); + Utils.checkNotNull(entry.getValue(), "value of attachments"); + } + return new AutoValue_AggregationData_DistributionData_Exemplar( + value, timestamp, attachmentsCopy); + } + } + } + + /** + * The last value of aggregated {@code MeasureValueDouble}s. + * + * @since 0.13 + */ + @Immutable + @AutoValue + public abstract static class LastValueDataDouble extends AggregationData { + + LastValueDataDouble() {} + + /** + * Creates a {@code LastValueDataDouble}. + * + * @param lastValue the last value. + * @return a {@code LastValueDataDouble}. + * @since 0.13 + */ + public static LastValueDataDouble create(double lastValue) { + return new AutoValue_AggregationData_LastValueDataDouble(lastValue); + } + + /** + * Returns the last value. + * + * @return the last value. + * @since 0.13 + */ + public abstract double getLastValue(); + + @Override + public final <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction) { + return p4.apply(this); + } + } + + /** + * The last value of aggregated {@code MeasureValueLong}s. + * + * @since 0.13 + */ + @Immutable + @AutoValue + public abstract static class LastValueDataLong extends AggregationData { + + LastValueDataLong() {} + + /** + * Creates a {@code LastValueDataLong}. + * + * @param lastValue the last value. + * @return a {@code LastValueDataLong}. + * @since 0.13 + */ + public static LastValueDataLong create(long lastValue) { + return new AutoValue_AggregationData_LastValueDataLong(lastValue); + } + + /** + * Returns the last value. + * + * @return the last value. + * @since 0.13 + */ + public abstract long getLastValue(); + + @Override + public final <T> T match( + Function<? super SumDataDouble, T> p0, + Function<? super SumDataLong, T> p1, + Function<? super CountData, T> p2, + Function<? super DistributionData, T> p3, + Function<? super LastValueDataDouble, T> p4, + Function<? super LastValueDataLong, T> p5, + Function<? super AggregationData, T> defaultFunction) { + return p5.apply(this); + } + } +} diff --git a/api/src/main/java/io/opencensus/stats/BucketBoundaries.java b/api/src/main/java/io/opencensus/stats/BucketBoundaries.java new file mode 100644 index 00000000..61e21e6c --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/BucketBoundaries.java @@ -0,0 +1,66 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.Utils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * The bucket boundaries for a histogram. + * + * @since 0.8 + */ +@Immutable +@AutoValue +public abstract class BucketBoundaries { + + /** + * Returns a {@code BucketBoundaries} with the given buckets. + * + * @param bucketBoundaries the boundaries for the buckets in the underlying histogram. + * @return a new {@code BucketBoundaries} with the specified boundaries. + * @throws NullPointerException if {@code bucketBoundaries} is null. + * @throws IllegalArgumentException if {@code bucketBoundaries} is not sorted. + * @since 0.8 + */ + public static final BucketBoundaries create(List<Double> bucketBoundaries) { + Utils.checkNotNull(bucketBoundaries, "bucketBoundaries"); + List<Double> bucketBoundariesCopy = new ArrayList<Double>(bucketBoundaries); // Deep copy. + // Check if sorted. + if (bucketBoundariesCopy.size() > 1) { + double lower = bucketBoundariesCopy.get(0); + for (int i = 1; i < bucketBoundariesCopy.size(); i++) { + double next = bucketBoundariesCopy.get(i); + Utils.checkArgument(lower < next, "Bucket boundaries not sorted."); + lower = next; + } + } + return new AutoValue_BucketBoundaries(Collections.unmodifiableList(bucketBoundariesCopy)); + } + + /** + * Returns a list of histogram bucket boundaries. + * + * @return a list of histogram bucket boundaries. + * @since 0.8 + */ + public abstract List<Double> getBoundaries(); +} diff --git a/api/src/main/java/io/opencensus/stats/Measure.java b/api/src/main/java/io/opencensus/stats/Measure.java new file mode 100644 index 00000000..2de7fd70 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/Measure.java @@ -0,0 +1,177 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Function; +import io.opencensus.internal.DefaultVisibilityForTesting; +import io.opencensus.internal.StringUtils; +import io.opencensus.internal.Utils; +import javax.annotation.concurrent.Immutable; + +/** + * The definition of the {@link Measurement} that is taken by OpenCensus library. + * + * @since 0.8 + */ +@Immutable +public abstract class Measure { + @DefaultVisibilityForTesting static final int NAME_MAX_LENGTH = 255; + private static final String ERROR_MESSAGE_INVALID_NAME = + "Name should be a ASCII string with a length no greater than " + + NAME_MAX_LENGTH + + " characters."; + + /** + * Applies the given match function to the underlying data type. + * + * @since 0.8 + */ + public abstract <T> T match( + Function<? super MeasureDouble, T> p0, + Function<? super MeasureLong, T> p1, + Function<? super Measure, T> defaultFunction); + + /** + * Name of measure, as a {@code String}. Should be a ASCII string with a length no greater than + * 255 characters. + * + * <p>Suggested format for name: {@code <web_host>/<path>}. + * + * @since 0.8 + */ + public abstract String getName(); + + /** + * Detailed description of the measure, used in documentation. + * + * @since 0.8 + */ + public abstract String getDescription(); + + /** + * The units in which {@link Measure} values are measured. + * + * <p>The suggested grammar for a unit is as follows: + * + * <ul> + * <li>Expression = Component { "." Component } {"/" Component }; + * <li>Component = [ PREFIX ] UNIT [ Annotation ] | Annotation | "1"; + * <li>Annotation = "{" NAME "}" ; + * </ul> + * + * <p>For example, string “MBy{transmitted}/ms” stands for megabytes per milliseconds, and the + * annotation transmitted inside {} is just a comment of the unit. + * + * @since 0.8 + */ + // TODO(songya): determine whether we want to check the grammar on string unit. + public abstract String getUnit(); + + // Prevents this class from being subclassed anywhere else. + private Measure() {} + + /** + * {@link Measure} with {@code Double} typed values. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class MeasureDouble extends Measure { + + MeasureDouble() {} + + /** + * Constructs a new {@link MeasureDouble}. + * + * @param name name of {@code Measure}. Suggested format: {@code <web_host>/<path>}. + * @param description description of {@code Measure}. + * @param unit unit of {@code Measure}. + * @return a {@code MeasureDouble}. + * @since 0.8 + */ + public static MeasureDouble create(String name, String description, String unit) { + Utils.checkArgument( + StringUtils.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH, + ERROR_MESSAGE_INVALID_NAME); + return new AutoValue_Measure_MeasureDouble(name, description, unit); + } + + @Override + public <T> T match( + Function<? super MeasureDouble, T> p0, + Function<? super MeasureLong, T> p1, + Function<? super Measure, T> defaultFunction) { + return p0.apply(this); + } + + @Override + public abstract String getName(); + + @Override + public abstract String getDescription(); + + @Override + public abstract String getUnit(); + } + + /** + * {@link Measure} with {@code Long} typed values. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class MeasureLong extends Measure { + + MeasureLong() {} + + /** + * Constructs a new {@link MeasureLong}. + * + * @param name name of {@code Measure}. Suggested format: {@code <web_host>/<path>}. + * @param description description of {@code Measure}. + * @param unit unit of {@code Measure}. + * @return a {@code MeasureLong}. + * @since 0.8 + */ + public static MeasureLong create(String name, String description, String unit) { + Utils.checkArgument( + StringUtils.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH, + ERROR_MESSAGE_INVALID_NAME); + return new AutoValue_Measure_MeasureLong(name, description, unit); + } + + @Override + public <T> T match( + Function<? super MeasureDouble, T> p0, + Function<? super MeasureLong, T> p1, + Function<? super Measure, T> defaultFunction) { + return p1.apply(this); + } + + @Override + public abstract String getName(); + + @Override + public abstract String getDescription(); + + @Override + public abstract String getUnit(); + } +} diff --git a/api/src/main/java/io/opencensus/stats/MeasureMap.java b/api/src/main/java/io/opencensus/stats/MeasureMap.java new file mode 100644 index 00000000..beb84f06 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/MeasureMap.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import io.opencensus.internal.Utils; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import io.opencensus.tags.TagContext; +import javax.annotation.concurrent.NotThreadSafe; + +/** + * A map from {@link Measure}s to measured values to be recorded at the same time. + * + * @since 0.8 + */ +@NotThreadSafe +public abstract class MeasureMap { + + /** + * Associates the {@link MeasureDouble} with the given value. Subsequent updates to the same + * {@link MeasureDouble} will overwrite the previous value. + * + * @param measure the {@link MeasureDouble} + * @param value the value to be associated with {@code measure} + * @return this + * @since 0.8 + */ + public abstract MeasureMap put(MeasureDouble measure, double value); + + /** + * Associates the {@link MeasureLong} with the given value. Subsequent updates to the same {@link + * MeasureLong} will overwrite the previous value. + * + * @param measure the {@link MeasureLong} + * @param value the value to be associated with {@code measure} + * @return this + * @since 0.8 + */ + public abstract MeasureMap put(MeasureLong measure, long value); + + /** + * Associate the contextual information of an {@code Exemplar} to this {@link MeasureMap}. + * Contextual information is represented as {@code String} key-value pairs. + * + * <p>If this method is called multiple times with the same key, only the last value will be kept. + * + * @param key the key of contextual information of an {@code Exemplar}. + * @param value the string representation of contextual information of an {@code Exemplar}. + * @return this + * @since 0.16 + */ + // TODO(songya): make this method abstract in the 0.17 release. + public MeasureMap putAttachment(String key, String value) { + // Provides a default no-op implementation to avoid breaking other existing sub-classes. + Utils.checkNotNull(key, "key"); + Utils.checkNotNull(value, "value"); + return this; + } + + /** + * Records all of the measures at the same time, with the current {@link TagContext}. + * + * <p>This method records all of the stats in the {@code MeasureMap} every time it is called. + * + * @since 0.8 + */ + public abstract void record(); + + /** + * Records all of the measures at the same time, with an explicit {@link TagContext}. + * + * <p>This method records all of the stats in the {@code MeasureMap} every time it is called. + * + * @param tags the tags associated with the measurements. + * @since 0.8 + */ + public abstract void record(TagContext tags); +} diff --git a/api/src/main/java/io/opencensus/stats/Measurement.java b/api/src/main/java/io/opencensus/stats/Measurement.java new file mode 100644 index 00000000..647a667d --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/Measurement.java @@ -0,0 +1,130 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Function; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import javax.annotation.concurrent.Immutable; + +/** + * Immutable representation of a Measurement. + * + * @since 0.8 + */ +@Immutable +public abstract class Measurement { + + /** + * Applies the given match function to the underlying data type. + * + * @since 0.8 + */ + public abstract <T> T match( + Function<? super MeasurementDouble, T> p0, + Function<? super MeasurementLong, T> p1, + Function<? super Measurement, T> defaultFunction); + + /** + * Extracts the measured {@link Measure}. + * + * @since 0.8 + */ + public abstract Measure getMeasure(); + + // Prevents this class from being subclassed anywhere else. + private Measurement() {} + + /** + * {@code Double} typed {@link Measurement}. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class MeasurementDouble extends Measurement { + MeasurementDouble() {} + + /** + * Constructs a new {@link MeasurementDouble}. + * + * @since 0.8 + */ + public static MeasurementDouble create(MeasureDouble measure, double value) { + return new AutoValue_Measurement_MeasurementDouble(measure, value); + } + + @Override + public abstract MeasureDouble getMeasure(); + + /** + * Returns the value for the measure. + * + * @return the value for the measure. + * @since 0.8 + */ + public abstract double getValue(); + + @Override + public <T> T match( + Function<? super MeasurementDouble, T> p0, + Function<? super MeasurementLong, T> p1, + Function<? super Measurement, T> defaultFunction) { + return p0.apply(this); + } + } + + /** + * {@code Long} typed {@link Measurement}. + * + * @since 0.8 + */ + @Immutable + @AutoValue + public abstract static class MeasurementLong extends Measurement { + MeasurementLong() {} + + /** + * Constructs a new {@link MeasurementLong}. + * + * @since 0.8 + */ + public static MeasurementLong create(MeasureLong measure, long value) { + return new AutoValue_Measurement_MeasurementLong(measure, value); + } + + @Override + public abstract MeasureLong getMeasure(); + + /** + * Returns the value for the measure. + * + * @return the value for the measure. + * @since 0.8 + */ + public abstract long getValue(); + + @Override + public <T> T match( + Function<? super MeasurementDouble, T> p0, + Function<? super MeasurementLong, T> p1, + Function<? super Measurement, T> defaultFunction) { + return p1.apply(this); + } + } +} diff --git a/api/src/main/java/io/opencensus/stats/NoopStats.java b/api/src/main/java/io/opencensus/stats/NoopStats.java new file mode 100644 index 00000000..e7e94a38 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/NoopStats.java @@ -0,0 +1,221 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import io.opencensus.common.Functions; +import io.opencensus.common.Timestamp; +import io.opencensus.internal.Utils; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import io.opencensus.tags.TagContext; +import io.opencensus.tags.TagValue; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** No-op implementations of stats classes. */ +final class NoopStats { + + private NoopStats() {} + + /** + * Returns a {@code StatsComponent} that has a no-op implementation for {@link StatsRecorder}. + * + * @return a {@code StatsComponent} that has a no-op implementation for {@code StatsRecorder}. + */ + static StatsComponent newNoopStatsComponent() { + return new NoopStatsComponent(); + } + + /** + * Returns a {@code StatsRecorder} that does not record any data. + * + * @return a {@code StatsRecorder} that does not record any data. + */ + static StatsRecorder getNoopStatsRecorder() { + return NoopStatsRecorder.INSTANCE; + } + + /** + * Returns a {@code MeasureMap} that ignores all calls to {@link MeasureMap#put}. + * + * @return a {@code MeasureMap} that ignores all calls to {@code MeasureMap#put}. + */ + static MeasureMap getNoopMeasureMap() { + return NoopMeasureMap.INSTANCE; + } + + /** + * Returns a {@code ViewManager} that maintains a map of views, but always returns empty {@link + * ViewData}s. + * + * @return a {@code ViewManager} that maintains a map of views, but always returns empty {@code + * ViewData}s. + */ + static ViewManager newNoopViewManager() { + return new NoopViewManager(); + } + + @ThreadSafe + private static final class NoopStatsComponent extends StatsComponent { + private final ViewManager viewManager = newNoopViewManager(); + private volatile boolean isRead; + + @Override + public ViewManager getViewManager() { + return viewManager; + } + + @Override + public StatsRecorder getStatsRecorder() { + return getNoopStatsRecorder(); + } + + @Override + public StatsCollectionState getState() { + isRead = true; + return StatsCollectionState.DISABLED; + } + + @Override + @Deprecated + public void setState(StatsCollectionState state) { + Utils.checkNotNull(state, "state"); + Utils.checkState(!isRead, "State was already read, cannot set state."); + } + } + + @Immutable + private static final class NoopStatsRecorder extends StatsRecorder { + static final StatsRecorder INSTANCE = new NoopStatsRecorder(); + + @Override + public MeasureMap newMeasureMap() { + return getNoopMeasureMap(); + } + } + + @Immutable + private static final class NoopMeasureMap extends MeasureMap { + static final MeasureMap INSTANCE = new NoopMeasureMap(); + + @Override + public MeasureMap put(MeasureDouble measure, double value) { + return this; + } + + @Override + public MeasureMap put(MeasureLong measure, long value) { + return this; + } + + @Override + public void record() {} + + @Override + public void record(TagContext tags) { + Utils.checkNotNull(tags, "tags"); + } + } + + @ThreadSafe + private static final class NoopViewManager extends ViewManager { + private static final Timestamp ZERO_TIMESTAMP = Timestamp.create(0, 0); + + @GuardedBy("registeredViews") + private final Map<View.Name, View> registeredViews = new HashMap<View.Name, View>(); + + // Cached set of exported views. It must be set to null whenever a view is registered or + // unregistered. + @javax.annotation.Nullable private volatile Set<View> exportedViews; + + @Override + public void registerView(View newView) { + Utils.checkNotNull(newView, "newView"); + synchronized (registeredViews) { + exportedViews = null; + View existing = registeredViews.get(newView.getName()); + Utils.checkArgument( + existing == null || newView.equals(existing), + "A different view with the same name already exists."); + if (existing == null) { + registeredViews.put(newView.getName(), newView); + } + } + } + + @Override + @javax.annotation.Nullable + @SuppressWarnings("deprecation") + public ViewData getView(View.Name name) { + Utils.checkNotNull(name, "name"); + synchronized (registeredViews) { + View view = registeredViews.get(name); + if (view == null) { + return null; + } else { + return ViewData.create( + view, + Collections.<List</*@Nullable*/ TagValue>, AggregationData>emptyMap(), + view.getWindow() + .match( + Functions.<ViewData.AggregationWindowData>returnConstant( + ViewData.AggregationWindowData.CumulativeData.create( + ZERO_TIMESTAMP, ZERO_TIMESTAMP)), + Functions.<ViewData.AggregationWindowData>returnConstant( + ViewData.AggregationWindowData.IntervalData.create(ZERO_TIMESTAMP)), + Functions.<ViewData.AggregationWindowData>throwAssertionError())); + } + } + } + + @Override + public Set<View> getAllExportedViews() { + Set<View> views = exportedViews; + if (views == null) { + synchronized (registeredViews) { + exportedViews = views = filterExportedViews(registeredViews.values()); + } + } + return views; + } + + // Returns the subset of the given views that should be exported + @SuppressWarnings("deprecation") + private static Set<View> filterExportedViews(Collection<View> allViews) { + Set<View> views = new HashSet<View>(); + for (View view : allViews) { + if (view.getWindow() instanceof View.AggregationWindow.Interval) { + continue; + } + views.add(view); + } + return Collections.unmodifiableSet(views); + } + } +} diff --git a/api/src/main/java/io/opencensus/stats/Stats.java b/api/src/main/java/io/opencensus/stats/Stats.java new file mode 100644 index 00000000..8393f631 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/Stats.java @@ -0,0 +1,126 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import io.opencensus.internal.DefaultVisibilityForTesting; +import io.opencensus.internal.Provider; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Class for accessing the default {@link StatsComponent}. + * + * @since 0.8 + */ +public final class Stats { + private static final Logger logger = Logger.getLogger(Stats.class.getName()); + + private static final StatsComponent statsComponent = + loadStatsComponent(StatsComponent.class.getClassLoader()); + + /** + * Returns the default {@link StatsRecorder}. + * + * @since 0.8 + */ + public static StatsRecorder getStatsRecorder() { + return statsComponent.getStatsRecorder(); + } + + /** + * Returns the default {@link ViewManager}. + * + * @since 0.8 + */ + public static ViewManager getViewManager() { + return statsComponent.getViewManager(); + } + + /** + * Returns the current {@code StatsCollectionState}. + * + * <p>When no implementation is available, {@code getState} always returns {@link + * StatsCollectionState#DISABLED}. + * + * <p>Once {@link #getState()} is called, subsequent calls to {@link + * #setState(StatsCollectionState)} will throw an {@code IllegalStateException}. + * + * @return the current {@code StatsCollectionState}. + * @since 0.8 + */ + public static StatsCollectionState getState() { + return statsComponent.getState(); + } + + /** + * Sets the current {@code StatsCollectionState}. + * + * <p>When no implementation is available, {@code setState} does not change the state. + * + * <p>If state is set to {@link StatsCollectionState#DISABLED}, all stats that are previously + * recorded will be cleared. + * + * @param state the new {@code StatsCollectionState}. + * @throws IllegalStateException if {@link #getState()} was previously called. + * @deprecated This method is deprecated because other libraries could cache the result of {@link + * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in + * initialization. This method throws {@link IllegalStateException} after {@code getState()} + * has been called, in order to limit changes to the result of {@code getState()}. + * @since 0.8 + */ + @Deprecated + public static void setState(StatsCollectionState state) { + statsComponent.setState(state); + } + + // Any provider that may be used for StatsComponent can be added here. + @DefaultVisibilityForTesting + static StatsComponent loadStatsComponent(@Nullable ClassLoader classLoader) { + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impl.stats.StatsComponentImpl", /*initialize=*/ true, classLoader), + StatsComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load full implementation for StatsComponent, now trying to load lite " + + "implementation.", + e); + } + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impllite.stats.StatsComponentImplLite", + /*initialize=*/ true, + classLoader), + StatsComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load lite implementation for StatsComponent, now using " + + "default implementation for StatsComponent.", + e); + } + return NoopStats.newNoopStatsComponent(); + } + + private Stats() {} +} diff --git a/api/src/main/java/io/opencensus/stats/StatsCollectionState.java b/api/src/main/java/io/opencensus/stats/StatsCollectionState.java new file mode 100644 index 00000000..6b2f2409 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/StatsCollectionState.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +/** + * State of the {@link StatsComponent}. + * + * @since 0.8 + */ +public enum StatsCollectionState { + + /** + * State that fully enables stats collection. + * + * <p>The {@link StatsComponent} collects stats for registered views. + * + * @since 0.8 + */ + ENABLED, + + /** + * State that disables stats collection. + * + * <p>The {@link StatsComponent} does not need to collect stats for registered views and may + * return empty {@link ViewData}s from {@link ViewManager#getView(View.Name)}. + * + * @since 0.8 + */ + DISABLED +} diff --git a/api/src/main/java/io/opencensus/stats/StatsComponent.java b/api/src/main/java/io/opencensus/stats/StatsComponent.java new file mode 100644 index 00000000..9764fce5 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/StatsComponent.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +/** + * Class that holds the implementations for {@link ViewManager} and {@link StatsRecorder}. + * + * <p>All objects returned by methods on {@code StatsComponent} are cacheable. + * + * @since 0.8 + */ +public abstract class StatsComponent { + + /** + * Returns the default {@link ViewManager}. + * + * @since 0.8 + */ + public abstract ViewManager getViewManager(); + + /** + * Returns the default {@link StatsRecorder}. + * + * @since 0.8 + */ + public abstract StatsRecorder getStatsRecorder(); + + /** + * Returns the current {@code StatsCollectionState}. + * + * <p>When no implementation is available, {@code getState} always returns {@link + * StatsCollectionState#DISABLED}. + * + * <p>Once {@link #getState()} is called, subsequent calls to {@link + * #setState(StatsCollectionState)} will throw an {@code IllegalStateException}. + * + * @return the current {@code StatsCollectionState}. + * @since 0.8 + */ + public abstract StatsCollectionState getState(); + + /** + * Sets the current {@code StatsCollectionState}. + * + * <p>When no implementation is available, {@code setState} does not change the state. + * + * <p>If state is set to {@link StatsCollectionState#DISABLED}, all stats that are previously + * recorded will be cleared. + * + * @param state the new {@code StatsCollectionState}. + * @throws IllegalStateException if {@link #getState()} was previously called. + * @deprecated This method is deprecated because other libraries could cache the result of {@link + * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in + * initialization. This method throws {@link IllegalStateException} after {@code getState()} + * has been called, in order to limit changes to the result of {@code getState()}. + * @since 0.8 + */ + @Deprecated + public abstract void setState(StatsCollectionState state); +} diff --git a/api/src/main/java/io/opencensus/stats/StatsRecorder.java b/api/src/main/java/io/opencensus/stats/StatsRecorder.java new file mode 100644 index 00000000..87b8c8b0 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/StatsRecorder.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +/** + * Provides methods to record stats against tags. + * + * @since 0.8 + */ +public abstract class StatsRecorder { + // TODO(sebright): Should we provide convenience methods for only recording one measure? + + /** + * Returns an object for recording multiple measurements. + * + * @return an object for recording multiple measurements. + * @since 0.8 + */ + public abstract MeasureMap newMeasureMap(); +} diff --git a/api/src/main/java/io/opencensus/stats/View.java b/api/src/main/java/io/opencensus/stats/View.java new file mode 100644 index 00000000..f563ff9a --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/View.java @@ -0,0 +1,306 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Duration; +import io.opencensus.common.Function; +import io.opencensus.internal.DefaultVisibilityForTesting; +import io.opencensus.internal.StringUtils; +import io.opencensus.internal.Utils; +import io.opencensus.tags.TagKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * A View specifies an aggregation and a set of tag keys. The aggregation will be broken down by the + * unique set of matching tag values for each measure. + * + * @since 0.8 + */ +@Immutable +@AutoValue +@AutoValue.CopyAnnotations +@SuppressWarnings("deprecation") +public abstract class View { + + @DefaultVisibilityForTesting static final int NAME_MAX_LENGTH = 255; + + private static final Comparator<TagKey> TAG_KEY_COMPARATOR = + new Comparator<TagKey>() { + @Override + public int compare(TagKey key1, TagKey key2) { + return key1.getName().compareTo(key2.getName()); + } + }; + + View() {} + + /** + * Name of view. Must be unique. + * + * @since 0.8 + */ + public abstract Name getName(); + + /** + * More detailed description, for documentation purposes. + * + * @since 0.8 + */ + public abstract String getDescription(); + + /** + * Measure type of this view. + * + * @since 0.8 + */ + public abstract Measure getMeasure(); + + /** + * The {@link Aggregation} associated with this {@link View}. + * + * @since 0.8 + */ + public abstract Aggregation getAggregation(); + + /** + * Columns (a.k.a Tag Keys) to match with the associated {@link Measure}. + * + * <p>{@link Measure} will be recorded in a "greedy" way. That is, every view aggregates every + * measure. This is similar to doing a GROUPBY on view’s columns. Columns must be unique. + * + * @since 0.8 + */ + public abstract List<TagKey> getColumns(); + + /** + * Returns the time {@link AggregationWindow} for this {@code View}. + * + * @return the time {@link AggregationWindow}. + * @since 0.8 + * @deprecated since 0.13. In the future all {@link View}s will be cumulative. + */ + @Deprecated + public abstract AggregationWindow getWindow(); + + /** + * Constructs a new {@link View}. + * + * @param name the {@link Name} of view. Must be unique. + * @param description the description of view. + * @param measure the {@link Measure} to be aggregated by this view. + * @param aggregation the basic {@link Aggregation} that this view will support. + * @param columns the {@link TagKey}s that this view will aggregate on. Columns should not contain + * duplicates. + * @param window the {@link AggregationWindow} of view. + * @return a new {@link View}. + * @since 0.8 + * @deprecated in favor of {@link #create(Name, String, Measure, Aggregation, List)}. + */ + @Deprecated + public static View create( + Name name, + String description, + Measure measure, + Aggregation aggregation, + List<TagKey> columns, + AggregationWindow window) { + Utils.checkArgument( + new HashSet<TagKey>(columns).size() == columns.size(), "Columns have duplicate."); + + List<TagKey> tagKeys = new ArrayList<TagKey>(columns); + Collections.sort(tagKeys, TAG_KEY_COMPARATOR); + return new AutoValue_View( + name, description, measure, aggregation, Collections.unmodifiableList(tagKeys), window); + } + + /** + * Constructs a new {@link View}. + * + * @param name the {@link Name} of view. Must be unique. + * @param description the description of view. + * @param measure the {@link Measure} to be aggregated by this view. + * @param aggregation the basic {@link Aggregation} that this view will support. + * @param columns the {@link TagKey}s that this view will aggregate on. Columns should not contain + * duplicates. + * @return a new {@link View}. + * @since 0.13 + */ + public static View create( + Name name, + String description, + Measure measure, + Aggregation aggregation, + List<TagKey> columns) { + Utils.checkArgument( + new HashSet<TagKey>(columns).size() == columns.size(), "Columns have duplicate."); + return create( + name, description, measure, aggregation, columns, AggregationWindow.Cumulative.create()); + } + + /** + * The name of a {@code View}. + * + * @since 0.8 + */ + // This type should be used as the key when associating data with Views. + @Immutable + @AutoValue + public abstract static class Name { + + Name() {} + + /** + * Returns the name as a {@code String}. + * + * @return the name as a {@code String}. + * @since 0.8 + */ + public abstract String asString(); + + /** + * Creates a {@code View.Name} from a {@code String}. Should be a ASCII string with a length no + * greater than 255 characters. + * + * <p>Suggested format for name: {@code <web_host>/<path>}. + * + * @param name the name {@code String}. + * @return a {@code View.Name} with the given name {@code String}. + * @since 0.8 + */ + public static Name create(String name) { + Utils.checkArgument( + StringUtils.isPrintableString(name) && name.length() <= NAME_MAX_LENGTH, + "Name should be a ASCII string with a length no greater than 255 characters."); + return new AutoValue_View_Name(name); + } + } + + /** + * The time window for a {@code View}. + * + * @since 0.8 + * @deprecated since 0.13. In the future all {@link View}s will be cumulative. + */ + @Deprecated + @Immutable + public abstract static class AggregationWindow { + + private AggregationWindow() {} + + /** + * Applies the given match function to the underlying data type. + * + * @since 0.8 + */ + public abstract <T> T match( + Function<? super Cumulative, T> p0, + Function<? super Interval, T> p1, + Function<? super AggregationWindow, T> defaultFunction); + + /** + * Cumulative (infinite interval) time {@code AggregationWindow}. + * + * @since 0.8 + * @deprecated since 0.13. In the future all {@link View}s will be cumulative. + */ + @Deprecated + @Immutable + @AutoValue + @AutoValue.CopyAnnotations + public abstract static class Cumulative extends AggregationWindow { + + private static final Cumulative CUMULATIVE = + new AutoValue_View_AggregationWindow_Cumulative(); + + Cumulative() {} + + /** + * Constructs a cumulative {@code AggregationWindow} that does not have an explicit {@code + * Duration}. Instead, cumulative {@code AggregationWindow} always has an interval of infinite + * {@code Duration}. + * + * @return a cumulative {@code AggregationWindow}. + * @since 0.8 + */ + public static Cumulative create() { + return CUMULATIVE; + } + + @Override + public final <T> T match( + Function<? super Cumulative, T> p0, + Function<? super Interval, T> p1, + Function<? super AggregationWindow, T> defaultFunction) { + return p0.apply(this); + } + } + + /** + * Interval (finite interval) time {@code AggregationWindow}. + * + * @since 0.8 + * @deprecated since 0.13. In the future all {@link View}s will be cumulative. + */ + @Deprecated + @Immutable + @AutoValue + @AutoValue.CopyAnnotations + public abstract static class Interval extends AggregationWindow { + + private static final Duration ZERO = Duration.create(0, 0); + + Interval() {} + + /** + * Returns the {@code Duration} associated with this {@code Interval}. + * + * @return a {@code Duration}. + * @since 0.8 + */ + public abstract Duration getDuration(); + + /** + * Constructs an interval {@code AggregationWindow} that has a finite explicit {@code + * Duration}. + * + * <p>The {@code Duration} should be able to round to milliseconds. Currently interval window + * cannot have smaller {@code Duration} such as microseconds or nanoseconds. + * + * @return an interval {@code AggregationWindow}. + * @since 0.8 + */ + public static Interval create(Duration duration) { + Utils.checkArgument(duration.compareTo(ZERO) > 0, "Duration must be positive"); + return new AutoValue_View_AggregationWindow_Interval(duration); + } + + @Override + public final <T> T match( + Function<? super Cumulative, T> p0, + Function<? super Interval, T> p1, + Function<? super AggregationWindow, T> defaultFunction) { + return p1.apply(this); + } + } + } +} diff --git a/api/src/main/java/io/opencensus/stats/ViewData.java b/api/src/main/java/io/opencensus/stats/ViewData.java new file mode 100644 index 00000000..df6edaa7 --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/ViewData.java @@ -0,0 +1,461 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Duration; +import io.opencensus.common.Function; +import io.opencensus.common.Functions; +import io.opencensus.common.Timestamp; +import io.opencensus.stats.Aggregation.Count; +import io.opencensus.stats.Aggregation.Distribution; +import io.opencensus.stats.Aggregation.LastValue; +import io.opencensus.stats.Aggregation.Sum; +import io.opencensus.stats.AggregationData.CountData; +import io.opencensus.stats.AggregationData.DistributionData; +import io.opencensus.stats.AggregationData.LastValueDataDouble; +import io.opencensus.stats.AggregationData.LastValueDataLong; +import io.opencensus.stats.AggregationData.SumDataDouble; +import io.opencensus.stats.AggregationData.SumDataLong; +import io.opencensus.stats.Measure.MeasureDouble; +import io.opencensus.stats.Measure.MeasureLong; +import io.opencensus.tags.TagValue; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.concurrent.Immutable; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * The aggregated data for a particular {@link View}. + * + * @since 0.8 + */ +@Immutable +@AutoValue +@AutoValue.CopyAnnotations +@SuppressWarnings("deprecation") +public abstract class ViewData { + + // Prevents this class from being subclassed anywhere else. + ViewData() {} + + /** + * The {@link View} associated with this {@link ViewData}. + * + * @since 0.8 + */ + public abstract View getView(); + + /** + * The {@link AggregationData} grouped by combination of tag values, associated with this {@link + * ViewData}. + * + * @since 0.8 + */ + public abstract Map<List</*@Nullable*/ TagValue>, AggregationData> getAggregationMap(); + + /** + * Returns the {@link AggregationWindowData} associated with this {@link ViewData}. + * + * <p>{@link AggregationWindowData} is deprecated since 0.13, please avoid using this method. Use + * {@link #getStart()} and {@link #getEnd()} instead. + * + * @return the {@code AggregationWindowData}. + * @since 0.8 + * @deprecated in favor of {@link #getStart()} and {@link #getEnd()}. + */ + @Deprecated + public abstract AggregationWindowData getWindowData(); + + /** + * Returns the start {@code Timestamp} for a {@link ViewData}. + * + * @return the start {@code Timestamp}. + * @since 0.13 + */ + public abstract Timestamp getStart(); + + /** + * Returns the end {@code Timestamp} for a {@link ViewData}. + * + * @return the end {@code Timestamp}. + * @since 0.13 + */ + public abstract Timestamp getEnd(); + + /** + * Constructs a new {@link ViewData}. + * + * @param view the {@link View} associated with this {@link ViewData}. + * @param map the mapping from {@link TagValue} list to {@link AggregationData}. + * @param windowData the {@link AggregationWindowData}. + * @return a {@code ViewData}. + * @throws IllegalArgumentException if the types of {@code Aggregation} and {@code + * AggregationData} don't match, or the types of {@code Window} and {@code WindowData} don't + * match. + * @since 0.8 + * @deprecated in favor of {@link #create(View, Map, Timestamp, Timestamp)}. + */ + @Deprecated + public static ViewData create( + final View view, + Map<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> map, + final AggregationWindowData windowData) { + checkWindow(view.getWindow(), windowData); + final Map<List</*@Nullable*/ TagValue>, AggregationData> deepCopy = + new HashMap<List</*@Nullable*/ TagValue>, AggregationData>(); + for (Entry<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> entry : + map.entrySet()) { + checkAggregation(view.getAggregation(), entry.getValue(), view.getMeasure()); + deepCopy.put( + Collections.unmodifiableList(new ArrayList</*@Nullable*/ TagValue>(entry.getKey())), + entry.getValue()); + } + return windowData.match( + new Function<ViewData.AggregationWindowData.CumulativeData, ViewData>() { + @Override + public ViewData apply(ViewData.AggregationWindowData.CumulativeData arg) { + return createInternal( + view, Collections.unmodifiableMap(deepCopy), arg, arg.getStart(), arg.getEnd()); + } + }, + new Function<ViewData.AggregationWindowData.IntervalData, ViewData>() { + @Override + public ViewData apply(ViewData.AggregationWindowData.IntervalData arg) { + Duration duration = ((View.AggregationWindow.Interval) view.getWindow()).getDuration(); + return createInternal( + view, + Collections.unmodifiableMap(deepCopy), + arg, + arg.getEnd() + .addDuration(Duration.create(-duration.getSeconds(), -duration.getNanos())), + arg.getEnd()); + } + }, + Functions.<ViewData>throwAssertionError()); + } + + /** + * Constructs a new {@link ViewData}. + * + * @param view the {@link View} associated with this {@link ViewData}. + * @param map the mapping from {@link TagValue} list to {@link AggregationData}. + * @param start the start {@link Timestamp} for this {@link ViewData}. + * @param end the end {@link Timestamp} for this {@link ViewData}. + * @return a {@code ViewData}. + * @throws IllegalArgumentException if the types of {@code Aggregation} and {@code + * AggregationData} don't match. + * @since 0.13 + */ + public static ViewData create( + View view, + Map<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> map, + Timestamp start, + Timestamp end) { + Map<List</*@Nullable*/ TagValue>, AggregationData> deepCopy = + new HashMap<List</*@Nullable*/ TagValue>, AggregationData>(); + for (Entry<? extends List</*@Nullable*/ TagValue>, ? extends AggregationData> entry : + map.entrySet()) { + checkAggregation(view.getAggregation(), entry.getValue(), view.getMeasure()); + deepCopy.put( + Collections.unmodifiableList(new ArrayList</*@Nullable*/ TagValue>(entry.getKey())), + entry.getValue()); + } + return createInternal( + view, + Collections.unmodifiableMap(deepCopy), + AggregationWindowData.CumulativeData.create(start, end), + start, + end); + } + + // Suppresses a nullness warning about calls to the AutoValue_ViewData constructor. The generated + // constructor does not have the @Nullable annotation on TagValue. + private static ViewData createInternal( + View view, + Map<List</*@Nullable*/ TagValue>, AggregationData> aggregationMap, + AggregationWindowData window, + Timestamp start, + Timestamp end) { + @SuppressWarnings("nullness") + Map<List<TagValue>, AggregationData> map = aggregationMap; + return new AutoValue_ViewData(view, map, window, start, end); + } + + private static void checkWindow( + View.AggregationWindow window, final AggregationWindowData windowData) { + window.match( + new Function<View.AggregationWindow.Cumulative, Void>() { + @Override + public Void apply(View.AggregationWindow.Cumulative arg) { + throwIfWindowMismatch( + windowData instanceof AggregationWindowData.CumulativeData, arg, windowData); + return null; + } + }, + new Function<View.AggregationWindow.Interval, Void>() { + @Override + public Void apply(View.AggregationWindow.Interval arg) { + throwIfWindowMismatch( + windowData instanceof AggregationWindowData.IntervalData, arg, windowData); + return null; + } + }, + Functions.</*@Nullable*/ Void>throwAssertionError()); + } + + private static void throwIfWindowMismatch( + boolean isValid, View.AggregationWindow window, AggregationWindowData windowData) { + if (!isValid) { + throw new IllegalArgumentException(createErrorMessageForWindow(window, windowData)); + } + } + + private static String createErrorMessageForWindow( + View.AggregationWindow window, AggregationWindowData windowData) { + return "AggregationWindow and AggregationWindowData types mismatch. " + + "AggregationWindow: " + + window.getClass().getSimpleName() + + " AggregationWindowData: " + + windowData.getClass().getSimpleName(); + } + + private static void checkAggregation( + final Aggregation aggregation, final AggregationData aggregationData, final Measure measure) { + aggregation.match( + new Function<Sum, Void>() { + @Override + public Void apply(Sum arg) { + measure.match( + new Function<MeasureDouble, Void>() { + @Override + public Void apply(MeasureDouble arg) { + throwIfAggregationMismatch( + aggregationData instanceof SumDataDouble, aggregation, aggregationData); + return null; + } + }, + new Function<MeasureLong, Void>() { + @Override + public Void apply(MeasureLong arg) { + throwIfAggregationMismatch( + aggregationData instanceof SumDataLong, aggregation, aggregationData); + return null; + } + }, + Functions.</*@Nullable*/ Void>throwAssertionError()); + return null; + } + }, + new Function<Count, Void>() { + @Override + public Void apply(Count arg) { + throwIfAggregationMismatch( + aggregationData instanceof CountData, aggregation, aggregationData); + return null; + } + }, + new Function<Distribution, Void>() { + @Override + public Void apply(Distribution arg) { + throwIfAggregationMismatch( + aggregationData instanceof DistributionData, aggregation, aggregationData); + return null; + } + }, + new Function<LastValue, Void>() { + @Override + public Void apply(LastValue arg) { + measure.match( + new Function<MeasureDouble, Void>() { + @Override + public Void apply(MeasureDouble arg) { + throwIfAggregationMismatch( + aggregationData instanceof LastValueDataDouble, + aggregation, + aggregationData); + return null; + } + }, + new Function<MeasureLong, Void>() { + @Override + public Void apply(MeasureLong arg) { + throwIfAggregationMismatch( + aggregationData instanceof LastValueDataLong, aggregation, aggregationData); + return null; + } + }, + Functions.</*@Nullable*/ Void>throwAssertionError()); + return null; + } + }, + new Function<Aggregation, Void>() { + @Override + public Void apply(Aggregation arg) { + // TODO(songya): remove this once Mean aggregation is completely removed. Before that + // we need to continue supporting Mean, since it could still be used by users and some + // deprecated RPC views. + if (arg instanceof Aggregation.Mean) { + throwIfAggregationMismatch( + aggregationData instanceof AggregationData.MeanData, + aggregation, + aggregationData); + return null; + } + throw new AssertionError(); + } + }); + } + + private static void throwIfAggregationMismatch( + boolean isValid, Aggregation aggregation, AggregationData aggregationData) { + if (!isValid) { + throw new IllegalArgumentException( + createErrorMessageForAggregation(aggregation, aggregationData)); + } + } + + private static String createErrorMessageForAggregation( + Aggregation aggregation, AggregationData aggregationData) { + return "Aggregation and AggregationData types mismatch. " + + "Aggregation: " + + aggregation.getClass().getSimpleName() + + " AggregationData: " + + aggregationData.getClass().getSimpleName(); + } + + /** + * The {@code AggregationWindowData} for a {@link ViewData}. + * + * @since 0.8 + * @deprecated since 0.13, please use start and end {@link Timestamp} instead. + */ + @Deprecated + @Immutable + public abstract static class AggregationWindowData { + + private AggregationWindowData() {} + + /** + * Applies the given match function to the underlying data type. + * + * @since 0.8 + */ + public abstract <T> T match( + Function<? super CumulativeData, T> p0, + Function<? super IntervalData, T> p1, + Function<? super AggregationWindowData, T> defaultFunction); + + /** + * Cumulative {@code AggregationWindowData}. + * + * @since 0.8 + * @deprecated since 0.13, please use start and end {@link Timestamp} instead. + */ + @Deprecated + @Immutable + @AutoValue + @AutoValue.CopyAnnotations + public abstract static class CumulativeData extends AggregationWindowData { + + CumulativeData() {} + + /** + * Returns the start {@code Timestamp} for a {@link CumulativeData}. + * + * @return the start {@code Timestamp}. + * @since 0.8 + */ + public abstract Timestamp getStart(); + + /** + * Returns the end {@code Timestamp} for a {@link CumulativeData}. + * + * @return the end {@code Timestamp}. + * @since 0.8 + */ + public abstract Timestamp getEnd(); + + @Override + public final <T> T match( + Function<? super CumulativeData, T> p0, + Function<? super IntervalData, T> p1, + Function<? super AggregationWindowData, T> defaultFunction) { + return p0.apply(this); + } + + /** + * Constructs a new {@link CumulativeData}. + * + * @since 0.8 + */ + public static CumulativeData create(Timestamp start, Timestamp end) { + if (start.compareTo(end) > 0) { + throw new IllegalArgumentException("Start time is later than end time."); + } + return new AutoValue_ViewData_AggregationWindowData_CumulativeData(start, end); + } + } + + /** + * Interval {@code AggregationWindowData}. + * + * @since 0.8 + * @deprecated since 0.13, please use start and end {@link Timestamp} instead. + */ + @Deprecated + @Immutable + @AutoValue + @AutoValue.CopyAnnotations + public abstract static class IntervalData extends AggregationWindowData { + + IntervalData() {} + + /** + * Returns the end {@code Timestamp} for an {@link IntervalData}. + * + * @return the end {@code Timestamp}. + * @since 0.8 + */ + public abstract Timestamp getEnd(); + + @Override + public final <T> T match( + Function<? super CumulativeData, T> p0, + Function<? super IntervalData, T> p1, + Function<? super AggregationWindowData, T> defaultFunction) { + return p1.apply(this); + } + + /** + * Constructs a new {@link IntervalData}. + * + * @since 0.8 + */ + public static IntervalData create(Timestamp end) { + return new AutoValue_ViewData_AggregationWindowData_IntervalData(end); + } + } + } +} diff --git a/api/src/main/java/io/opencensus/stats/ViewManager.java b/api/src/main/java/io/opencensus/stats/ViewManager.java new file mode 100644 index 00000000..a00165cc --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/ViewManager.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.stats; + +import java.util.Set; +import javax.annotation.Nullable; + +/** + * Provides facilities to register {@link View}s for collecting stats and retrieving stats data as a + * {@link ViewData}. + * + * @since 0.8 + */ +public abstract class ViewManager { + /** + * Pull model for stats. Registers a {@link View} that will collect data to be accessed via {@link + * #getView(View.Name)}. + * + * @param view the {@code View} to be registered. + * @since 0.8 + */ + public abstract void registerView(View view); + + /** + * Returns the current stats data, {@link ViewData}, associated with the given view name. + * + * <p>Returns {@code null} if the {@code View} is not registered. + * + * @param view the name of {@code View} for the current stats. + * @return {@code ViewData} for the {@code View}, or {@code null} if the {@code View} is not + * registered. + * @since 0.8 + */ + @Nullable + public abstract ViewData getView(View.Name view); + + /** + * Returns all registered views that should be exported. + * + * <p>This method should be used by any stats exporter that automatically exports data for views + * registered with the {@link ViewManager}. + * + * @return all registered views that should be exported. + * @since 0.9 + */ + public abstract Set<View> getAllExportedViews(); +} diff --git a/api/src/main/java/io/opencensus/stats/package-info.java b/api/src/main/java/io/opencensus/stats/package-info.java new file mode 100644 index 00000000..981daa0e --- /dev/null +++ b/api/src/main/java/io/opencensus/stats/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** API for stats recording. */ +// TODO: Add more details. +// TODO: Add code examples. +package io.opencensus.stats; diff --git a/api/src/main/java/io/opencensus/tags/InternalUtils.java b/api/src/main/java/io/opencensus/tags/InternalUtils.java new file mode 100644 index 00000000..944122e1 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/InternalUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import java.util.Iterator; + +/** + * Internal tagging utilities. + * + * @since 0.8 + */ +@io.opencensus.common.Internal +public final class InternalUtils { + private InternalUtils() {} + + /** + * Internal tag accessor. + * + * @since 0.8 + */ + public static Iterator<Tag> getTags(TagContext tags) { + return tags.getIterator(); + } +} diff --git a/api/src/main/java/io/opencensus/tags/NoopTags.java b/api/src/main/java/io/opencensus/tags/NoopTags.java new file mode 100644 index 00000000..fb52b164 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/NoopTags.java @@ -0,0 +1,214 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import io.opencensus.common.Scope; +import io.opencensus.internal.NoopScope; +import io.opencensus.internal.Utils; +import io.opencensus.tags.propagation.TagContextBinarySerializer; +import io.opencensus.tags.propagation.TagPropagationComponent; +import java.util.Collections; +import java.util.Iterator; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/** No-op implementations of tagging classes. */ +final class NoopTags { + + private NoopTags() {} + + /** + * Returns a {@code TagsComponent} that has a no-op implementation for {@link Tagger}. + * + * @return a {@code TagsComponent} that has a no-op implementation for {@code Tagger}. + */ + static TagsComponent newNoopTagsComponent() { + return new NoopTagsComponent(); + } + + /** + * Returns a {@code Tagger} that only produces {@link TagContext}s with no tags. + * + * @return a {@code Tagger} that only produces {@code TagContext}s with no tags. + */ + static Tagger getNoopTagger() { + return NoopTagger.INSTANCE; + } + + /** + * Returns a {@code TagContextBuilder} that ignores all calls to {@link TagContextBuilder#put}. + * + * @return a {@code TagContextBuilder} that ignores all calls to {@link TagContextBuilder#put}. + */ + static TagContextBuilder getNoopTagContextBuilder() { + return NoopTagContextBuilder.INSTANCE; + } + + /** + * Returns a {@code TagContext} that does not contain any tags. + * + * @return a {@code TagContext} that does not contain any tags. + */ + static TagContext getNoopTagContext() { + return NoopTagContext.INSTANCE; + } + + /** Returns a {@code TagPropagationComponent} that contains no-op serializers. */ + static TagPropagationComponent getNoopTagPropagationComponent() { + return NoopTagPropagationComponent.INSTANCE; + } + + /** + * Returns a {@code TagContextBinarySerializer} that serializes all {@code TagContext}s to zero + * bytes and deserializes all inputs to empty {@code TagContext}s. + */ + static TagContextBinarySerializer getNoopTagContextBinarySerializer() { + return NoopTagContextBinarySerializer.INSTANCE; + } + + @ThreadSafe + private static final class NoopTagsComponent extends TagsComponent { + private volatile boolean isRead; + + @Override + public Tagger getTagger() { + return getNoopTagger(); + } + + @Override + public TagPropagationComponent getTagPropagationComponent() { + return getNoopTagPropagationComponent(); + } + + @Override + public TaggingState getState() { + isRead = true; + return TaggingState.DISABLED; + } + + @Override + @Deprecated + public void setState(TaggingState state) { + Utils.checkNotNull(state, "state"); + Utils.checkState(!isRead, "State was already read, cannot set state."); + } + } + + @Immutable + private static final class NoopTagger extends Tagger { + static final Tagger INSTANCE = new NoopTagger(); + + @Override + public TagContext empty() { + return getNoopTagContext(); + } + + @Override + public TagContext getCurrentTagContext() { + return getNoopTagContext(); + } + + @Override + public TagContextBuilder emptyBuilder() { + return getNoopTagContextBuilder(); + } + + @Override + public TagContextBuilder toBuilder(TagContext tags) { + Utils.checkNotNull(tags, "tags"); + return getNoopTagContextBuilder(); + } + + @Override + public TagContextBuilder currentBuilder() { + return getNoopTagContextBuilder(); + } + + @Override + public Scope withTagContext(TagContext tags) { + Utils.checkNotNull(tags, "tags"); + return NoopScope.getInstance(); + } + } + + @Immutable + private static final class NoopTagContextBuilder extends TagContextBuilder { + static final TagContextBuilder INSTANCE = new NoopTagContextBuilder(); + + @Override + public TagContextBuilder put(TagKey key, TagValue value) { + Utils.checkNotNull(key, "key"); + Utils.checkNotNull(value, "value"); + return this; + } + + @Override + public TagContextBuilder remove(TagKey key) { + Utils.checkNotNull(key, "key"); + return this; + } + + @Override + public TagContext build() { + return getNoopTagContext(); + } + + @Override + public Scope buildScoped() { + return NoopScope.getInstance(); + } + } + + @Immutable + private static final class NoopTagContext extends TagContext { + static final TagContext INSTANCE = new NoopTagContext(); + + // TODO(sebright): Is there any way to let the user know that their tags were ignored? + @Override + protected Iterator<Tag> getIterator() { + return Collections.<Tag>emptySet().iterator(); + } + } + + @Immutable + private static final class NoopTagPropagationComponent extends TagPropagationComponent { + static final TagPropagationComponent INSTANCE = new NoopTagPropagationComponent(); + + @Override + public TagContextBinarySerializer getBinarySerializer() { + return getNoopTagContextBinarySerializer(); + } + } + + @Immutable + private static final class NoopTagContextBinarySerializer extends TagContextBinarySerializer { + static final TagContextBinarySerializer INSTANCE = new NoopTagContextBinarySerializer(); + static final byte[] EMPTY_BYTE_ARRAY = {}; + + @Override + public byte[] toByteArray(TagContext tags) { + Utils.checkNotNull(tags, "tags"); + return EMPTY_BYTE_ARRAY; + } + + @Override + public TagContext fromByteArray(byte[] bytes) { + Utils.checkNotNull(bytes, "bytes"); + return getNoopTagContext(); + } + } +} diff --git a/api/src/main/java/io/opencensus/tags/Tag.java b/api/src/main/java/io/opencensus/tags/Tag.java new file mode 100644 index 00000000..9e0a7a82 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/Tag.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import com.google.auto.value.AutoValue; +import javax.annotation.concurrent.Immutable; + +/** + * {@link TagKey} paired with a {@link TagValue}. + * + * @since 0.8 + */ +@Immutable +@AutoValue +public abstract class Tag { + + Tag() {} + + /** + * Creates a {@code Tag} from the given key and value. + * + * @param key the tag key. + * @param value the tag value. + * @return a {@code Tag} with the given key and value. + * @since 0.8 + */ + public static Tag create(TagKey key, TagValue value) { + return new AutoValue_Tag(key, value); + } + + /** + * Returns the tag's key. + * + * @return the tag's key. + * @since 0.8 + */ + public abstract TagKey getKey(); + + /** + * Returns the tag's value. + * + * @return the tag's value. + * @since 0.8 + */ + public abstract TagValue getValue(); +} diff --git a/api/src/main/java/io/opencensus/tags/TagContext.java b/api/src/main/java/io/opencensus/tags/TagContext.java new file mode 100644 index 00000000..e36acdff --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TagContext.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import java.util.HashMap; +import java.util.Iterator; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A map from {@link TagKey} to {@link TagValue} that can be used to label anything that is + * associated with a specific operation. + * + * <p>For example, {@code TagContext}s can be used to label stats, log messages, or debugging + * information. + * + * @since 0.8 + */ +@Immutable +public abstract class TagContext { + + /** + * Returns an iterator over the tags in this {@code TagContext}. + * + * @return an iterator over the tags in this {@code TagContext}. + * @since 0.8 + */ + // This method is protected to prevent client code from accessing the tags of any TagContext. We + // don't currently support efficient access to tags. However, every TagContext subclass needs to + // provide access to its tags to the stats and tagging implementations by implementing this + // method. If we decide to support access to tags in the future, we can add a public iterator() + // method and implement it for all subclasses by calling getIterator(). + // + // The stats and tagging implementations can access any TagContext's tags through + // io.opencensus.tags.InternalUtils.getTags, which calls this method. + protected abstract Iterator<Tag> getIterator(); + + @Override + public String toString() { + return "TagContext"; + } + + /** + * Returns true iff the other object is an instance of {@code TagContext} and contains the same + * key-value pairs. Implementations are free to override this method to provide better + * performance. + */ + @Override + public boolean equals(@Nullable Object other) { + if (!(other instanceof TagContext)) { + return false; + } + TagContext otherTags = (TagContext) other; + Iterator<Tag> iter1 = getIterator(); + Iterator<Tag> iter2 = otherTags.getIterator(); + HashMap<Tag, Integer> tags = new HashMap<Tag, Integer>(); + while (iter1 != null && iter1.hasNext()) { + Tag tag = iter1.next(); + if (tags.containsKey(tag)) { + tags.put(tag, tags.get(tag) + 1); + } else { + tags.put(tag, 1); + } + } + while (iter2 != null && iter2.hasNext()) { + Tag tag = iter2.next(); + if (!tags.containsKey(tag)) { + return false; + } + int count = tags.get(tag); + if (count > 1) { + tags.put(tag, count - 1); + } else { + tags.remove(tag); + } + } + return tags.isEmpty(); + } + + @Override + public final int hashCode() { + int hashCode = 0; + Iterator<Tag> i = getIterator(); + if (i == null) { + return hashCode; + } + while (i.hasNext()) { + Tag tag = i.next(); + if (tag != null) { + hashCode += tag.hashCode(); + } + } + return hashCode; + } +} diff --git a/api/src/main/java/io/opencensus/tags/TagContextBuilder.java b/api/src/main/java/io/opencensus/tags/TagContextBuilder.java new file mode 100644 index 00000000..f4268968 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TagContextBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import io.opencensus.common.Scope; + +/** + * Builder for the {@link TagContext} class. + * + * @since 0.8 + */ +public abstract class TagContextBuilder { + + /** + * Adds the key/value pair regardless of whether the key is present. + * + * @param key the {@code TagKey} which will be set. + * @param value the {@code TagValue} to set for the given key. + * @return this + * @since 0.8 + */ + public abstract TagContextBuilder put(TagKey key, TagValue value); + + /** + * Removes the key if it exists. + * + * @param key the {@code TagKey} which will be removed. + * @return this + * @since 0.8 + */ + public abstract TagContextBuilder remove(TagKey key); + + /** + * Creates a {@code TagContext} from this builder. + * + * @return a {@code TagContext} with the same tags as this builder. + * @since 0.8 + */ + public abstract TagContext build(); + + /** + * Enters the scope of code where the {@link TagContext} created from this builder is in the + * current context and returns an object that represents that scope. The scope is exited when the + * returned object is closed. + * + * @return an object that defines a scope where the {@code TagContext} created from this builder + * is set to the current context. + * @since 0.8 + */ + public abstract Scope buildScoped(); +} diff --git a/api/src/main/java/io/opencensus/tags/TagKey.java b/api/src/main/java/io/opencensus/tags/TagKey.java new file mode 100644 index 00000000..ca4582bd --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TagKey.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.StringUtils; +import io.opencensus.internal.Utils; +import javax.annotation.concurrent.Immutable; + +/** + * A key to a value stored in a {@link TagContext}. + * + * <p>Each {@code TagKey} has a {@code String} name. Names have a maximum length of {@link + * #MAX_LENGTH} and contain only printable ASCII characters. + * + * <p>{@code TagKey}s are designed to be used as constants. Declaring each key as a constant + * prevents key names from being validated multiple times. + * + * @since 0.8 + */ +@Immutable +@AutoValue +public abstract class TagKey { + /** + * The maximum length for a tag key name. The value is {@value #MAX_LENGTH}. + * + * @since 0.8 + */ + public static final int MAX_LENGTH = 255; + + TagKey() {} + + /** + * Constructs a {@code TagKey} with the given name. + * + * <p>The name must meet the following requirements: + * + * <ol> + * <li>It cannot be longer than {@link #MAX_LENGTH}. + * <li>It can only contain printable ASCII characters. + * </ol> + * + * @param name the name of the key. + * @return a {@code TagKey} with the given name. + * @throws IllegalArgumentException if the name is not valid. + * @since 0.8 + */ + public static TagKey create(String name) { + Utils.checkArgument(isValid(name), "Invalid TagKey name: %s", name); + return new AutoValue_TagKey(name); + } + + /** + * Returns the name of the key. + * + * @return the name of the key. + * @since 0.8 + */ + public abstract String getName(); + + /** + * Determines whether the given {@code String} is a valid tag key. + * + * @param name the tag key name to be validated. + * @return whether the name is valid. + */ + private static boolean isValid(String name) { + return !name.isEmpty() && name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name); + } +} diff --git a/api/src/main/java/io/opencensus/tags/TagValue.java b/api/src/main/java/io/opencensus/tags/TagValue.java new file mode 100644 index 00000000..9111ca28 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TagValue.java @@ -0,0 +1,79 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.StringUtils; +import io.opencensus.internal.Utils; +import javax.annotation.concurrent.Immutable; + +/** + * A validated tag value. + * + * <p>Validation ensures that the {@code String} has a maximum length of {@link #MAX_LENGTH} and + * contains only printable ASCII characters. + * + * @since 0.8 + */ +@Immutable +@AutoValue +public abstract class TagValue { + /** + * The maximum length for a tag value. The value is {@value #MAX_LENGTH}. + * + * @since 0.8 + */ + public static final int MAX_LENGTH = 255; + + TagValue() {} + + /** + * Constructs a {@code TagValue} from the given string. The string must meet the following + * requirements: + * + * <ol> + * <li>It cannot be longer than {@link #MAX_LENGTH}. + * <li>It can only contain printable ASCII characters. + * </ol> + * + * @param value the tag value. + * @throws IllegalArgumentException if the {@code String} is not valid. + * @since 0.8 + */ + public static TagValue create(String value) { + Utils.checkArgument(isValid(value), "Invalid TagValue: %s", value); + return new AutoValue_TagValue(value); + } + + /** + * Returns the tag value as a {@code String}. + * + * @return the tag value as a {@code String}. + * @since 0.8 + */ + public abstract String asString(); + + /** + * Determines whether the given {@code String} is a valid tag value. + * + * @param value the tag value to be validated. + * @return whether the value is valid. + */ + private static boolean isValid(String value) { + return value.length() <= MAX_LENGTH && StringUtils.isPrintableString(value); + } +} diff --git a/api/src/main/java/io/opencensus/tags/Tagger.java b/api/src/main/java/io/opencensus/tags/Tagger.java new file mode 100644 index 00000000..f1e203ad --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/Tagger.java @@ -0,0 +1,86 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import io.opencensus.common.Scope; + +/** + * Object for creating new {@link TagContext}s and {@code TagContext}s based on the current context. + * + * <p>This class returns {@link TagContextBuilder builders} that can be used to create the + * implementation-dependent {@link TagContext}s. + * + * <p>Implementations may have different constraints and are free to convert tag contexts to their + * own subtypes. This means callers cannot assume the {@link #getCurrentTagContext() current + * context} is the same instance as the one {@link #withTagContext(TagContext) placed into scope}. + * + * @since 0.8 + */ +public abstract class Tagger { + + /** + * Returns an empty {@code TagContext}. + * + * @return an empty {@code TagContext}. + * @since 0.8 + */ + public abstract TagContext empty(); + + /** + * Returns the current {@code TagContext}. + * + * @return the current {@code TagContext}. + * @since 0.8 + */ + public abstract TagContext getCurrentTagContext(); + + /** + * Returns a new empty {@code Builder}. + * + * @return a new empty {@code Builder}. + * @since 0.8 + */ + public abstract TagContextBuilder emptyBuilder(); + + /** + * Returns a builder based on this {@code TagContext}. + * + * @return a builder based on this {@code TagContext}. + * @since 0.8 + */ + public abstract TagContextBuilder toBuilder(TagContext tags); + + /** + * Returns a new builder created from the current {@code TagContext}. + * + * @return a new builder created from the current {@code TagContext}. + * @since 0.8 + */ + public abstract TagContextBuilder currentBuilder(); + + /** + * Enters the scope of code where the given {@code TagContext} is in the current context + * (replacing the previous {@code TagContext}) and returns an object that represents that scope. + * The scope is exited when the returned object is closed. + * + * @param tags the {@code TagContext} to be set to the current context. + * @return an object that defines a scope where the given {@code TagContext} is set to the current + * context. + * @since 0.8 + */ + public abstract Scope withTagContext(TagContext tags); +} diff --git a/api/src/main/java/io/opencensus/tags/TaggingState.java b/api/src/main/java/io/opencensus/tags/TaggingState.java new file mode 100644 index 00000000..88970361 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TaggingState.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +/** + * State of the {@link TagsComponent}. + * + * @since 0.8 + */ +public enum TaggingState { + // TODO(sebright): Should we add a state that propagates the tags, but doesn't allow + // modifications? + + /** + * State that fully enables tagging. + * + * <p>The {@link TagsComponent} can add tags to {@link TagContext}s, propagate {@code TagContext}s + * in the current context, and serialize {@code TagContext}s. + * + * @since 0.8 + */ + ENABLED, + + /** + * State that disables tagging. + * + * <p>The {@link TagsComponent} may not add tags to {@link TagContext}s, propagate {@code + * TagContext}s in the current context, or serialize {@code TagContext}s. + * + * @since 0.8 + */ + // TODO(sebright): Document how this interacts with stats collection. + DISABLED +} diff --git a/api/src/main/java/io/opencensus/tags/Tags.java b/api/src/main/java/io/opencensus/tags/Tags.java new file mode 100644 index 00000000..07123647 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/Tags.java @@ -0,0 +1,126 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import io.opencensus.internal.DefaultVisibilityForTesting; +import io.opencensus.internal.Provider; +import io.opencensus.tags.propagation.TagPropagationComponent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Class for accessing the default {@link TagsComponent}. + * + * @since 0.8 + */ +public final class Tags { + private static final Logger logger = Logger.getLogger(Tags.class.getName()); + + private static final TagsComponent tagsComponent = + loadTagsComponent(TagsComponent.class.getClassLoader()); + + private Tags() {} + + /** + * Returns the default {@code Tagger}. + * + * @return the default {@code Tagger}. + * @since 0.8 + */ + public static Tagger getTagger() { + return tagsComponent.getTagger(); + } + + /** + * Returns the default {@code TagPropagationComponent}. + * + * @return the default {@code TagPropagationComponent}. + * @since 0.8 + */ + public static TagPropagationComponent getTagPropagationComponent() { + return tagsComponent.getTagPropagationComponent(); + } + + /** + * Returns the current {@code TaggingState}. + * + * <p>When no implementation is available, {@code getState} always returns {@link + * TaggingState#DISABLED}. + * + * <p>Once {@link #getState()} is called, subsequent calls to {@link #setState(TaggingState)} will + * throw an {@code IllegalStateException}. + * + * @return the current {@code TaggingState}. + * @since 0.8 + */ + public static TaggingState getState() { + return tagsComponent.getState(); + } + + /** + * Sets the current {@code TaggingState}. + * + * <p>When no implementation is available, {@code setState} does not change the state. + * + * @param state the new {@code TaggingState}. + * @throws IllegalStateException if {@link #getState()} was previously called. + * @deprecated This method is deprecated because other libraries could cache the result of {@link + * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in + * initialization. This method throws {@link IllegalStateException} after {@link #getState()} + * has been called, in order to limit changes to the result of {@code getState()}. + * @since 0.8 + */ + @Deprecated + public static void setState(TaggingState state) { + tagsComponent.setState(state); + } + + // Any provider that may be used for TagsComponent can be added here. + @DefaultVisibilityForTesting + static TagsComponent loadTagsComponent(@Nullable ClassLoader classLoader) { + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impl.tags.TagsComponentImpl", /*initialize=*/ true, classLoader), + TagsComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load full implementation for TagsComponent, now trying to load lite " + + "implementation.", + e); + } + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impllite.tags.TagsComponentImplLite", + /*initialize=*/ true, + classLoader), + TagsComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load lite implementation for TagsComponent, now using " + + "default implementation for TagsComponent.", + e); + } + return NoopTags.newNoopTagsComponent(); + } +} diff --git a/api/src/main/java/io/opencensus/tags/TagsComponent.java b/api/src/main/java/io/opencensus/tags/TagsComponent.java new file mode 100644 index 00000000..d34f1951 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/TagsComponent.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags; + +import io.opencensus.tags.propagation.TagPropagationComponent; + +/** + * Class that holds the implementation for {@link Tagger} and {@link TagPropagationComponent}. + * + * <p>All objects returned by methods on {@code TagsComponent} are cacheable. + * + * @since 0.8 + */ +public abstract class TagsComponent { + + /** + * Returns the {@link Tagger} for this implementation. + * + * @since 0.8 + */ + public abstract Tagger getTagger(); + + /** + * Returns the {@link TagPropagationComponent} for this implementation. + * + * @since 0.8 + */ + public abstract TagPropagationComponent getTagPropagationComponent(); + + /** + * Returns the current {@code TaggingState}. + * + * <p>When no implementation is available, {@code getState} always returns {@link + * TaggingState#DISABLED}. + * + * <p>Once {@link #getState()} is called, subsequent calls to {@link #setState(TaggingState)} will + * throw an {@code IllegalStateException}. + * + * @return the current {@code TaggingState}. + * @since 0.8 + */ + public abstract TaggingState getState(); + + /** + * Sets the current {@code TaggingState}. + * + * <p>When no implementation is available, {@code setState} does not change the state. + * + * @param state the new {@code TaggingState}. + * @throws IllegalStateException if {@link #getState()} was previously called. + * @deprecated This method is deprecated because other libraries could cache the result of {@link + * #getState()}, use a stale value, and behave incorrectly. It is only safe to call early in + * initialization. This method throws {@link IllegalStateException} after {@code getState()} + * has been called, in order to limit changes to the result of {@code getState()}. + * @since 0.8 + */ + @Deprecated + public abstract void setState(TaggingState state); +} diff --git a/api/src/main/java/io/opencensus/tags/package-info.java b/api/src/main/java/io/opencensus/tags/package-info.java new file mode 100644 index 00000000..eb19ee77 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/package-info.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * API for associating tags with scoped operations. + * + * <p>This package manages a set of tags in the {@code io.grpc.Context}. The tags can be used to + * label anything that is associated with a specific operation. For example, the {@code + * io.opencensus.stats} package labels all stats with the current tags. + * + * <p>{@link io.opencensus.tags.Tag Tags} are key-value pairs. The {@link io.opencensus.tags.TagKey + * keys} and {@link io.opencensus.tags.TagValue values} are wrapped {@code String}s. They are stored + * as a map in a {@link io.opencensus.tags.TagContext}. + * + * <p>Note that tags are independent of the tracing data that is propagated in the {@code + * io.grpc.Context}, such as trace ID. + */ +// TODO(sebright): Add code examples. +package io.opencensus.tags; diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java b/api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java new file mode 100644 index 00000000..39eb8cee --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextBinarySerializer.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags.propagation; + +import io.opencensus.tags.TagContext; + +/** + * Object for serializing and deserializing {@link TagContext}s with the binary format. + * + * <p>See <a + * href="https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md#tag-context">opencensus-specs</a> + * for the specification of the cross-language binary serialization format. + * + * @since 0.8 + */ +public abstract class TagContextBinarySerializer { + + /** + * Serializes the {@code TagContext} into the on-the-wire representation. + * + * <p>This method should be the inverse of {@link #fromByteArray}. + * + * @param tags the {@code TagContext} to serialize. + * @return the on-the-wire representation of a {@code TagContext}. + * @throws TagContextSerializationException if the result would be larger than the maximum allowed + * serialized size. + * @since 0.8 + */ + public abstract byte[] toByteArray(TagContext tags) throws TagContextSerializationException; + + /** + * Creates a {@code TagContext} from the given on-the-wire encoded representation. + * + * <p>This method should be the inverse of {@link #toByteArray}. + * + * @param bytes on-the-wire representation of a {@code TagContext}. + * @return a {@code TagContext} deserialized from {@code bytes}. + * @throws TagContextDeserializationException if there is a parse error, the input contains + * invalid tags, or the input is larger than the maximum allowed serialized size. + * @since 0.8 + */ + public abstract TagContext fromByteArray(byte[] bytes) throws TagContextDeserializationException; +} diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagContextDeserializationException.java b/api/src/main/java/io/opencensus/tags/propagation/TagContextDeserializationException.java new file mode 100644 index 00000000..11dcb59f --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextDeserializationException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags.propagation; + +import io.opencensus.tags.TagContext; + +/** + * Exception thrown when a {@link TagContext} cannot be parsed. + * + * @since 0.8 + */ +public final class TagContextDeserializationException extends Exception { + private static final long serialVersionUID = 0L; + + /** + * Constructs a new {@code TagContextParseException} with the given message. + * + * @param message a message describing the error. + * @since 0.8 + */ + public TagContextDeserializationException(String message) { + super(message); + } + + /** + * Constructs a new {@code TagContextParseException} with the given message and cause. + * + * @param message a message describing the error. + * @param cause the cause of the error. + * @since 0.8 + */ + public TagContextDeserializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagContextSerializationException.java b/api/src/main/java/io/opencensus/tags/propagation/TagContextSerializationException.java new file mode 100644 index 00000000..bb3c9b74 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/propagation/TagContextSerializationException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags.propagation; + +import io.opencensus.tags.TagContext; + +/** + * Exception thrown when a {@link TagContext} cannot be serialized. + * + * @since 0.8 + */ +public final class TagContextSerializationException extends Exception { + private static final long serialVersionUID = 0L; + + /** + * Constructs a new {@code TagContextSerializationException} with the given message. + * + * @param message a message describing the error. + * @since 0.8 + */ + public TagContextSerializationException(String message) { + super(message); + } + + /** + * Constructs a new {@code TagContextSerializationException} with the given message and cause. + * + * @param message a message describing the error. + * @param cause the cause of the error. + * @since 0.8 + */ + public TagContextSerializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java b/api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java new file mode 100644 index 00000000..6ececa79 --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/propagation/TagPropagationComponent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags.propagation; + +import io.opencensus.tags.TagContext; + +/** + * Object containing all supported {@link TagContext} propagation formats. + * + * @since 0.8 + */ +// TODO(sebright): Add an HTTP serializer. +public abstract class TagPropagationComponent { + + /** + * Returns the {@link TagContextBinarySerializer} for this implementation. + * + * @return the {@code TagContextBinarySerializer} for this implementation. + * @since 0.8 + */ + public abstract TagContextBinarySerializer getBinarySerializer(); +} diff --git a/api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java b/api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java new file mode 100644 index 00000000..8936bbbb --- /dev/null +++ b/api/src/main/java/io/opencensus/tags/unsafe/ContextUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.tags.unsafe; + +import io.grpc.Context; +import io.opencensus.tags.Tag; +import io.opencensus.tags.TagContext; +import java.util.Collections; +import java.util.Iterator; +import javax.annotation.concurrent.Immutable; + +/** + * Utility methods for accessing the {@link TagContext} contained in the {@link io.grpc.Context}. + * + * <p>Most code should interact with the current context via the public APIs in {@link + * io.opencensus.tags.TagContext} and avoid accessing {@link #TAG_CONTEXT_KEY} directly. + * + * @since 0.8 + */ +public final class ContextUtils { + private static final TagContext EMPTY_TAG_CONTEXT = new EmptyTagContext(); + + private ContextUtils() {} + + /** + * The {@link io.grpc.Context.Key} used to interact with the {@code TagContext} contained in the + * {@link io.grpc.Context}. + * + * @since 0.8 + */ + public static final Context.Key<TagContext> TAG_CONTEXT_KEY = + Context.keyWithDefault("opencensus-tag-context-key", EMPTY_TAG_CONTEXT); + + @Immutable + private static final class EmptyTagContext extends TagContext { + + @Override + protected Iterator<Tag> getIterator() { + return Collections.<Tag>emptySet().iterator(); + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/Annotation.java b/api/src/main/java/io/opencensus/trace/Annotation.java new file mode 100644 index 00000000..97f2fdd2 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Annotation.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.Utils; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.concurrent.Immutable; + +/** + * A text annotation with a set of attributes. + * + * @since 0.5 + */ +@Immutable +@AutoValue +public abstract class Annotation { + private static final Map<String, AttributeValue> EMPTY_ATTRIBUTES = + Collections.unmodifiableMap(Collections.<String, AttributeValue>emptyMap()); + + /** + * Returns a new {@code Annotation} with the given description. + * + * @param description the text description of the {@code Annotation}. + * @return a new {@code Annotation} with the given description. + * @throws NullPointerException if {@code description} is {@code null}. + * @since 0.5 + */ + public static Annotation fromDescription(String description) { + return new AutoValue_Annotation(description, EMPTY_ATTRIBUTES); + } + + /** + * Returns a new {@code Annotation} with the given description and set of attributes. + * + * @param description the text description of the {@code Annotation}. + * @param attributes the attributes of the {@code Annotation}. + * @return a new {@code Annotation} with the given description and set of attributes. + * @throws NullPointerException if {@code description} or {@code attributes} are {@code null}. + * @since 0.5 + */ + public static Annotation fromDescriptionAndAttributes( + String description, Map<String, AttributeValue> attributes) { + return new AutoValue_Annotation( + description, + Collections.unmodifiableMap( + new HashMap<String, AttributeValue>(Utils.checkNotNull(attributes, "attributes")))); + } + + /** + * Return the description of the {@code Annotation}. + * + * @return the description of the {@code Annotation}. + * @since 0.5 + */ + public abstract String getDescription(); + + /** + * Return the attributes of the {@code Annotation}. + * + * @return the attributes of the {@code Annotation}. + * @since 0.5 + */ + public abstract Map<String, AttributeValue> getAttributes(); + + Annotation() {} +} diff --git a/api/src/main/java/io/opencensus/trace/AttributeValue.java b/api/src/main/java/io/opencensus/trace/AttributeValue.java new file mode 100644 index 00000000..efa9d1df --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/AttributeValue.java @@ -0,0 +1,255 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Function; +import io.opencensus.internal.Utils; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents all the possible values for an attribute. An attribute can have 3 types + * of values: {@code String}, {@code Boolean} or {@code Long}. + * + * @since 0.5 + */ +@Immutable +public abstract class AttributeValue { + /** + * Returns an {@code AttributeValue} with a string value. + * + * @param stringValue The new value. + * @return an {@code AttributeValue} with a string value. + * @throws NullPointerException if {@code stringValue} is {@code null}. + * @since 0.5 + */ + public static AttributeValue stringAttributeValue(String stringValue) { + return AttributeValueString.create(stringValue); + } + + /** + * Returns an {@code AttributeValue} with a boolean value. + * + * @param booleanValue The new value. + * @return an {@code AttributeValue} with a boolean value. + * @since 0.5 + */ + public static AttributeValue booleanAttributeValue(boolean booleanValue) { + return AttributeValueBoolean.create(booleanValue); + } + + /** + * Returns an {@code AttributeValue} with a long value. + * + * @param longValue The new value. + * @return an {@code AttributeValue} with a long value. + * @since 0.5 + */ + public static AttributeValue longAttributeValue(long longValue) { + return AttributeValueLong.create(longValue); + } + + /** + * Returns an {@code AttributeValue} with a double value. + * + * @param doubleValue The new value. + * @return an {@code AttributeValue} with a double value. + * @since 0.17 + */ + public static AttributeValue doubleAttributeValue(double doubleValue) { + return AttributeValueDouble.create(doubleValue); + } + + AttributeValue() {} + + /** + * Applies a function to the underlying value. The function that is called depends on the value's + * type, which can be {@code String}, {@code Long}, or {@code Boolean}. + * + * @param stringFunction the function that should be applied if the value has type {@code String}. + * @param longFunction the function that should be applied if the value has type {@code Long}. + * @param booleanFunction the function that should be applied if the value has type {@code + * Boolean}. + * @param defaultFunction the function that should be applied if the value has a type that was + * added after this {@code match} method was added to the API. See {@link + * io.opencensus.common.Functions} for some common functions for handling unknown types. + * @return the result of the function applied to the underlying value. + * @since 0.5 + * @deprecated in favor of {@link #match(Function, Function, Function, Function, Function)}. + */ + @Deprecated + public abstract <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<Object, T> defaultFunction); + + /** + * Applies a function to the underlying value. The function that is called depends on the value's + * type, which can be {@code String}, {@code Long}, or {@code Boolean}. + * + * @param stringFunction the function that should be applied if the value has type {@code String}. + * @param longFunction the function that should be applied if the value has type {@code Long}. + * @param booleanFunction the function that should be applied if the value has type {@code + * Boolean}. + * @param doubleFunction the function that should be applied if the value has type {@code Double}. + * @param defaultFunction the function that should be applied if the value has a type that was + * added after this {@code match} method was added to the API. See {@link + * io.opencensus.common.Functions} for some common functions for handling unknown types. + * @return the result of the function applied to the underlying value. + * @since 0.17 + */ + @SuppressWarnings("InconsistentOverloads") + public abstract <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<? super Double, T> doubleFunction, + Function<Object, T> defaultFunction); + + @Immutable + @AutoValue + abstract static class AttributeValueString extends AttributeValue { + + AttributeValueString() {} + + static AttributeValue create(String stringValue) { + return new AutoValue_AttributeValue_AttributeValueString( + Utils.checkNotNull(stringValue, "stringValue")); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<Object, T> defaultFunction) { + return stringFunction.apply(getStringValue()); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<? super Double, T> doubleFunction, + Function<Object, T> defaultFunction) { + return stringFunction.apply(getStringValue()); + } + + abstract String getStringValue(); + } + + @Immutable + @AutoValue + abstract static class AttributeValueBoolean extends AttributeValue { + + AttributeValueBoolean() {} + + static AttributeValue create(Boolean booleanValue) { + return new AutoValue_AttributeValue_AttributeValueBoolean( + Utils.checkNotNull(booleanValue, "booleanValue")); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<Object, T> defaultFunction) { + return booleanFunction.apply(getBooleanValue()); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<? super Double, T> doubleFunction, + Function<Object, T> defaultFunction) { + return booleanFunction.apply(getBooleanValue()); + } + + abstract Boolean getBooleanValue(); + } + + @Immutable + @AutoValue + abstract static class AttributeValueLong extends AttributeValue { + + AttributeValueLong() {} + + static AttributeValue create(Long longValue) { + return new AutoValue_AttributeValue_AttributeValueLong( + Utils.checkNotNull(longValue, "longValue")); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<Object, T> defaultFunction) { + return longFunction.apply(getLongValue()); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<? super Double, T> doubleFunction, + Function<Object, T> defaultFunction) { + return longFunction.apply(getLongValue()); + } + + abstract Long getLongValue(); + } + + @Immutable + @AutoValue + abstract static class AttributeValueDouble extends AttributeValue { + + AttributeValueDouble() {} + + static AttributeValue create(Double doubleValue) { + return new AutoValue_AttributeValue_AttributeValueDouble( + Utils.checkNotNull(doubleValue, "doubleValue")); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<Object, T> defaultFunction) { + return defaultFunction.apply(getDoubleValue()); + } + + @Override + public final <T> T match( + Function<? super String, T> stringFunction, + Function<? super Boolean, T> booleanFunction, + Function<? super Long, T> longFunction, + Function<? super Double, T> doubleFunction, + Function<Object, T> defaultFunction) { + return doubleFunction.apply(getDoubleValue()); + } + + abstract Double getDoubleValue(); + } +} diff --git a/api/src/main/java/io/opencensus/trace/BaseMessageEvent.java b/api/src/main/java/io/opencensus/trace/BaseMessageEvent.java new file mode 100644 index 00000000..5ad961f6 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/BaseMessageEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +/** + * Superclass for {@link MessageEvent} and {@link NetworkEvent} to resolve API backward + * compatibility issue. + * + * <p>{@code SpanData.create} can't be overloaded with parameter types that differ only in the type + * of the TimedEvent, because the signatures are the same after generic type erasure. {@code + * BaseMessageEvent} allows the same method to accept both {@code TimedEvents<NetworkEvent>} and + * {@code TimedEvents<MessageEvent>}. + * + * <p>This class should only be extended by {@code NetworkEvent} and {@code MessageEvent}. + * + * @deprecated This class is for internal use only. + * @since 0.12 + */ +@Deprecated +public abstract class BaseMessageEvent { + // package protected to avoid users to extend it. + BaseMessageEvent() {} +} diff --git a/api/src/main/java/io/opencensus/trace/BlankSpan.java b/api/src/main/java/io/opencensus/trace/BlankSpan.java new file mode 100644 index 00000000..af6456d3 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/BlankSpan.java @@ -0,0 +1,102 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.internal.Utils; +import java.util.Map; +import javax.annotation.concurrent.Immutable; + +/** + * The {@code BlankSpan} is a singleton class, which is the default {@link Span} that is used when + * no {@code Span} implementation is available. All operations are no-op. + * + * <p>Used also to stop tracing, see {@link Tracer#withSpan}. + * + * @since 0.5 + */ +@Immutable +public final class BlankSpan extends Span { + /** + * Singleton instance of this class. + * + * @since 0.5 + */ + public static final BlankSpan INSTANCE = new BlankSpan(); + + private BlankSpan() { + super(SpanContext.INVALID, null); + } + + /** No-op implementation of the {@link Span#putAttribute(String, AttributeValue)} method. */ + @Override + public void putAttribute(String key, AttributeValue value) { + Utils.checkNotNull(key, "key"); + Utils.checkNotNull(value, "value"); + } + + /** No-op implementation of the {@link Span#putAttributes(Map)} method. */ + @Override + public void putAttributes(Map<String, AttributeValue> attributes) { + Utils.checkNotNull(attributes, "attributes"); + } + + /** No-op implementation of the {@link Span#addAnnotation(String, Map)} method. */ + @Override + public void addAnnotation(String description, Map<String, AttributeValue> attributes) { + Utils.checkNotNull(description, "description"); + Utils.checkNotNull(attributes, "attributes"); + } + + /** No-op implementation of the {@link Span#addAnnotation(Annotation)} method. */ + @Override + public void addAnnotation(Annotation annotation) { + Utils.checkNotNull(annotation, "annotation"); + } + + /** No-op implementation of the {@link Span#addNetworkEvent(NetworkEvent)} method. */ + @Override + @Deprecated + public void addNetworkEvent(NetworkEvent networkEvent) {} + + /** No-op implementation of the {@link Span#addMessageEvent(MessageEvent)} method. */ + @Override + public void addMessageEvent(MessageEvent messageEvent) { + Utils.checkNotNull(messageEvent, "messageEvent"); + } + + /** No-op implementation of the {@link Span#addLink(Link)} method. */ + @Override + public void addLink(Link link) { + Utils.checkNotNull(link, "link"); + } + + @Override + public void setStatus(Status status) { + Utils.checkNotNull(status, "status"); + } + + /** No-op implementation of the {@link Span#end(EndSpanOptions)} method. */ + @Override + public void end(EndSpanOptions options) { + Utils.checkNotNull(options, "options"); + } + + @Override + public String toString() { + return "BlankSpan"; + } +} diff --git a/api/src/main/java/io/opencensus/trace/CurrentSpanUtils.java b/api/src/main/java/io/opencensus/trace/CurrentSpanUtils.java new file mode 100644 index 00000000..aa2f055a --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/CurrentSpanUtils.java @@ -0,0 +1,180 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.grpc.Context; +import io.opencensus.common.Scope; +import io.opencensus.trace.unsafe.ContextUtils; +import java.util.concurrent.Callable; +import javax.annotation.Nullable; + +/** Util methods/functionality to interact with the {@link Span} in the {@link io.grpc.Context}. */ +final class CurrentSpanUtils { + // No instance of this class. + private CurrentSpanUtils() {} + + /** + * Returns The {@link Span} from the current context. + * + * @return The {@code Span} from the current context. + */ + @Nullable + static Span getCurrentSpan() { + return ContextUtils.CONTEXT_SPAN_KEY.get(); + } + + /** + * Enters the scope of code where the given {@link Span} is in the current context, and returns an + * object that represents that scope. The scope is exited when the returned object is closed. + * + * <p>Supports try-with-resource idiom. + * + * @param span The {@code Span} to be set to the current context. + * @param endSpan if {@code true} the returned {@code Scope} will close the {@code Span}. + * @return An object that defines a scope where the given {@code Span} is set to the current + * context. + */ + static Scope withSpan(Span span, boolean endSpan) { + return new ScopeInSpan(span, endSpan); + } + + /** + * Wraps a {@link Runnable} so that it executes with the {@code span} as the current {@code Span}. + * + * @param span the {@code Span} to be set as current. + * @param endSpan if {@code true} the returned {@code Runnable} will close the {@code Span}. + * @param runnable the {@code Runnable} to run in the {@code Span}. + * @return the wrapped {@code Runnable}. + */ + static Runnable withSpan(Span span, boolean endSpan, Runnable runnable) { + return new RunnableInSpan(span, runnable, endSpan); + } + + /** + * Wraps a {@link Callable} so that it executes with the {@code span} as the current {@code Span}. + * + * @param span the {@code Span} to be set as current. + * @param endSpan if {@code true} the returned {@code Runnable} will close the {@code Span}. + * @param callable the {@code Callable} to run in the {@code Span}. + * @return the wrapped {@code Callable}. + */ + static <C> Callable<C> withSpan(Span span, boolean endSpan, Callable<C> callable) { + return new CallableInSpan<C>(span, callable, endSpan); + } + + // Defines an arbitrary scope of code as a traceable operation. Supports try-with-resources idiom. + private static final class ScopeInSpan implements Scope { + private final Context origContext; + private final Span span; + private final boolean endSpan; + + /** + * Constructs a new {@link ScopeInSpan}. + * + * @param span is the {@code Span} to be added to the current {@code io.grpc.Context}. + */ + private ScopeInSpan(Span span, boolean endSpan) { + this.span = span; + this.endSpan = endSpan; + origContext = Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach(); + } + + @Override + public void close() { + Context.current().detach(origContext); + if (endSpan) { + span.end(); + } + } + } + + private static final class RunnableInSpan implements Runnable { + // TODO(bdrutu): Investigate if the extra private visibility increases the generated bytecode. + private final Span span; + private final Runnable runnable; + private final boolean endSpan; + + private RunnableInSpan(Span span, Runnable runnable, boolean endSpan) { + this.span = span; + this.runnable = runnable; + this.endSpan = endSpan; + } + + @Override + public void run() { + Context origContext = + Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach(); + try { + runnable.run(); + } catch (Throwable t) { + setErrorStatus(span, t); + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else if (t instanceof Error) { + throw (Error) t; + } + throw new RuntimeException("unexpected", t); + } finally { + Context.current().detach(origContext); + if (endSpan) { + span.end(); + } + } + } + } + + private static final class CallableInSpan<V> implements Callable<V> { + private final Span span; + private final Callable<V> callable; + private final boolean endSpan; + + private CallableInSpan(Span span, Callable<V> callable, boolean endSpan) { + this.span = span; + this.callable = callable; + this.endSpan = endSpan; + } + + @Override + public V call() throws Exception { + Context origContext = + Context.current().withValue(ContextUtils.CONTEXT_SPAN_KEY, span).attach(); + try { + return callable.call(); + } catch (Exception e) { + setErrorStatus(span, e); + throw e; + } catch (Throwable t) { + setErrorStatus(span, t); + if (t instanceof Error) { + throw (Error) t; + } + throw new RuntimeException("unexpected", t); + } finally { + Context.current().detach(origContext); + if (endSpan) { + span.end(); + } + } + } + } + + private static void setErrorStatus(Span span, Throwable t) { + span.setStatus( + Status.UNKNOWN.withDescription( + t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage())); + } +} diff --git a/api/src/main/java/io/opencensus/trace/EndSpanOptions.java b/api/src/main/java/io/opencensus/trace/EndSpanOptions.java new file mode 100644 index 00000000..b0d9a470 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/EndSpanOptions.java @@ -0,0 +1,127 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import java.util.Collection; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A class that enables overriding the default values used when ending a {@link Span}. Allows + * overriding the {@link Status status}. + * + * @since 0.5 + */ +@Immutable +@AutoValue +public abstract class EndSpanOptions { + /** + * The default {@code EndSpanOptions}. + * + * @since 0.5 + */ + public static final EndSpanOptions DEFAULT = builder().build(); + + /** + * Returns a new {@link Builder} with default options. + * + * @return a new {@code Builder} with default options. + * @since 0.5 + */ + public static Builder builder() { + return new AutoValue_EndSpanOptions.Builder().setSampleToLocalSpanStore(false); + } + + /** + * If {@code true} this is equivalent with calling the {@link + * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} in + * advance for this span name. + * + * <p>It is strongly recommended to use the {@link + * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} API + * instead. + * + * @return {@code true} if the name of the {@code Span} should be registered to the {@code + * io.opencensus.trace.export.SampledSpanStore}. + * @since 0.8 + */ + @ExperimentalApi + public abstract boolean getSampleToLocalSpanStore(); + + /** + * Returns the status. + * + * <p>If {@code null} then the {@link Span} will record the {@link Status} set via {@link + * Span#setStatus(Status)} or the default {@link Status#OK} if no status was set. + * + * @return the status. + * @since 0.5 + */ + @Nullable + public abstract Status getStatus(); + + /** + * Builder class for {@link EndSpanOptions}. + * + * @since 0.5 + */ + @AutoValue.Builder + public abstract static class Builder { + /** + * Sets the status for the {@link Span}. + * + * <p>If set, this will override the status set via {@link Span#setStatus(Status)}. + * + * @param status the status. + * @return this. + * @since 0.5 + */ + public abstract Builder setStatus(Status status); + + /** + * If set to {@code true} this is equivalent with calling the {@link + * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} in + * advance for the given span name. + * + * <p>WARNING: setting this option to a randomly generated span name can OOM your process + * because the library will save samples for each name. + * + * <p>It is strongly recommended to use the {@link + * io.opencensus.trace.export.SampledSpanStore#registerSpanNamesForCollection(Collection)} API + * instead. + * + * @return this. + * @since 0.8 + */ + @ExperimentalApi + public abstract Builder setSampleToLocalSpanStore(boolean sampleToLocalSpanStore); + + /** + * Builds and returns a {@code EndSpanOptions} with the desired settings. + * + * @return a {@code EndSpanOptions} with the desired settings. + * @since 0.5 + */ + public abstract EndSpanOptions build(); + + Builder() {} + } + + EndSpanOptions() {} +} diff --git a/api/src/main/java/io/opencensus/trace/Link.java b/api/src/main/java/io/opencensus/trace/Link.java new file mode 100644 index 00000000..1de79710 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Link.java @@ -0,0 +1,124 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.auto.value.AutoValue; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.concurrent.Immutable; + +/** + * A link to a {@link Span} from a different trace. + * + * <p>It requires a {@link Type} which describes the relationship with the linked {@code Span} and + * the identifiers of the linked {@code Span}. + * + * <p>Used (for example) in batching operations, where a single batch handler processes multiple + * requests from different traces. + * + * @since 0.5 + */ +@Immutable +@AutoValue +public abstract class Link { + private static final Map<String, AttributeValue> EMPTY_ATTRIBUTES = Collections.emptyMap(); + + /** + * The relationship with the linked {@code Span} relative to the current {@code Span}. + * + * @since 0.5 + */ + public enum Type { + /** + * When the linked {@code Span} is a child of the current {@code Span}. + * + * @since 0.5 + */ + CHILD_LINKED_SPAN, + /** + * When the linked {@code Span} is a parent of the current {@code Span}. + * + * @since 0.5 + */ + PARENT_LINKED_SPAN + } + + /** + * Returns a new {@code Link}. + * + * @param context the context of the linked {@code Span}. + * @param type the type of the relationship with the linked {@code Span}. + * @return a new {@code Link}. + * @since 0.5 + */ + public static Link fromSpanContext(SpanContext context, Type type) { + return new AutoValue_Link(context.getTraceId(), context.getSpanId(), type, EMPTY_ATTRIBUTES); + } + + /** + * Returns a new {@code Link}. + * + * @param context the context of the linked {@code Span}. + * @param type the type of the relationship with the linked {@code Span}. + * @param attributes the attributes of the {@code Link}. + * @return a new {@code Link}. + * @since 0.5 + */ + public static Link fromSpanContext( + SpanContext context, Type type, Map<String, AttributeValue> attributes) { + return new AutoValue_Link( + context.getTraceId(), + context.getSpanId(), + type, + Collections.unmodifiableMap(new HashMap<String, AttributeValue>(attributes))); + } + + /** + * Returns the {@code TraceId}. + * + * @return the {@code TraceId}. + * @since 0.5 + */ + public abstract TraceId getTraceId(); + + /** + * Returns the {@code SpanId}. + * + * @return the {@code SpanId} + * @since 0.5 + */ + public abstract SpanId getSpanId(); + + /** + * Returns the {@code Type}. + * + * @return the {@code Type}. + * @since 0.5 + */ + public abstract Type getType(); + + /** + * Returns the set of attributes. + * + * @return the set of attributes. + * @since 0.5 + */ + public abstract Map<String, AttributeValue> getAttributes(); + + Link() {} +} diff --git a/api/src/main/java/io/opencensus/trace/LowerCaseBase16Encoding.java b/api/src/main/java/io/opencensus/trace/LowerCaseBase16Encoding.java new file mode 100644 index 00000000..bca95868 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/LowerCaseBase16Encoding.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.internal.Utils; +import java.util.Arrays; + +/** Internal copy of the Guava implementation of the {@code BaseEncoding.base16().lowerCase()}. */ +final class LowerCaseBase16Encoding { + private static final String ALPHABET = "0123456789abcdef"; + private static final int ASCII_CHARACTERS = 128; + private static final char[] ENCODING = buildEncodingArray(); + private static final byte[] DECODING = buildDecodingArray(); + + private static char[] buildEncodingArray() { + char[] encoding = new char[512]; + for (int i = 0; i < 256; ++i) { + encoding[i] = ALPHABET.charAt(i >>> 4); + encoding[i | 0x100] = ALPHABET.charAt(i & 0xF); + } + return encoding; + } + + private static byte[] buildDecodingArray() { + byte[] decoding = new byte[ASCII_CHARACTERS]; + Arrays.fill(decoding, (byte) -1); + for (int i = 0; i < ALPHABET.length(); i++) { + char c = ALPHABET.charAt(i); + decoding[c] = (byte) i; + } + return decoding; + } + + /** + * Encodes the specified byte array, and returns the encoded {@code String}. + * + * @param bytes byte array to be encoded. + * @return the encoded {@code String}. + */ + static String encodeToString(byte[] bytes) { + StringBuilder stringBuilder = new StringBuilder(bytes.length * 2); + for (byte byteVal : bytes) { + int b = byteVal & 0xFF; + stringBuilder.append(ENCODING[b]); + stringBuilder.append(ENCODING[b | 0x100]); + } + return stringBuilder.toString(); + } + + /** + * Decodes the specified character sequence, and returns the resulting {@code byte[]}. + * + * @param chars the character sequence to be decoded. + * @return the resulting {@code byte[]} + * @throws IllegalArgumentException if the input is not a valid encoded string according to this + * encoding. + */ + static byte[] decodeToBytes(CharSequence chars) { + Utils.checkArgument(chars.length() % 2 == 0, "Invalid input length " + chars.length()); + int bytesWritten = 0; + byte[] bytes = new byte[chars.length() / 2]; + for (int i = 0; i < chars.length(); i += 2) { + bytes[bytesWritten++] = decodeByte(chars.charAt(i), chars.charAt(i + 1)); + } + return bytes; + } + + private static byte decodeByte(char hi, char lo) { + Utils.checkArgument(lo < ASCII_CHARACTERS && DECODING[lo] != -1, "Invalid character " + lo); + Utils.checkArgument(hi < ASCII_CHARACTERS && DECODING[hi] != -1, "Invalid character " + hi); + int decoded = DECODING[hi] << 4 | DECODING[lo]; + return (byte) decoded; + } + + // Private constructor to disallow instances. + private LowerCaseBase16Encoding() {} +} diff --git a/api/src/main/java/io/opencensus/trace/MessageEvent.java b/api/src/main/java/io/opencensus/trace/MessageEvent.java new file mode 100644 index 00000000..4b693aaa --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/MessageEvent.java @@ -0,0 +1,151 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.Utils; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents a generic messaging event. This class can represent messaging happened in + * any layer, especially higher application layer. Thus, it can be used when recording events in + * pipeline works, in-process bidirectional streams and batch processing. + * + * <p>It requires a {@link Type type} and a message id that serves to uniquely identify each + * message. It can optionally have information about the message size. + * + * @since 0.12 + */ +@Immutable +@AutoValue +@SuppressWarnings("deprecation") +public abstract class MessageEvent extends BaseMessageEvent { + /** + * Available types for a {@code MessageEvent}. + * + * @since 0.12 + */ + public enum Type { + /** + * When the message was sent. + * + * @since 0.12 + */ + SENT, + /** + * When the message was received. + * + * @since 0.12 + */ + RECEIVED, + } + + /** + * Returns a new {@link Builder} with default values. + * + * @param type designates whether this is a send or receive message. + * @param messageId serves to uniquely identify each message. + * @return a new {@code Builder} with default values. + * @throws NullPointerException if {@code type} is {@code null}. + * @since 0.12 + */ + public static Builder builder(Type type, long messageId) { + return new AutoValue_MessageEvent.Builder() + .setType(Utils.checkNotNull(type, "type")) + .setMessageId(messageId) + // We need to set a value for the message size because the autovalue requires all + // primitives to be initialized. + .setUncompressedMessageSize(0) + .setCompressedMessageSize(0); + } + + /** + * Returns the type of the {@code MessageEvent}. + * + * @return the type of the {@code MessageEvent}. + * @since 0.12 + */ + public abstract Type getType(); + + /** + * Returns the message id argument that serves to uniquely identify each message. + * + * @return the message id of the {@code MessageEvent}. + * @since 0.12 + */ + public abstract long getMessageId(); + + /** + * Returns the uncompressed size in bytes of the {@code MessageEvent}. + * + * @return the uncompressed size in bytes of the {@code MessageEvent}. + * @since 0.12 + */ + public abstract long getUncompressedMessageSize(); + + /** + * Returns the compressed size in bytes of the {@code MessageEvent}. + * + * @return the compressed size in bytes of the {@code MessageEvent}. + * @since 0.12 + */ + public abstract long getCompressedMessageSize(); + + /** + * Builder class for {@link MessageEvent}. + * + * @since 0.12 + */ + @AutoValue.Builder + public abstract static class Builder { + // Package protected methods because these values are mandatory and set only in the + // MessageEvent#builder() function. + abstract Builder setType(Type type); + + abstract Builder setMessageId(long messageId); + + /** + * Sets the uncompressed message size. + * + * @param uncompressedMessageSize represents the uncompressed size in bytes of this message. + * @return this. + * @since 0.12 + */ + public abstract Builder setUncompressedMessageSize(long uncompressedMessageSize); + + /** + * Sets the compressed message size. + * + * @param compressedMessageSize represents the compressed size in bytes of this message. + * @return this. + * @since 0.12 + */ + public abstract Builder setCompressedMessageSize(long compressedMessageSize); + + /** + * Builds and returns a {@code MessageEvent} with the desired values. + * + * @return a {@code MessageEvent} with the desired values. + * @since 0.12 + */ + public abstract MessageEvent build(); + + Builder() {} + } + + MessageEvent() {} +} diff --git a/api/src/main/java/io/opencensus/trace/NetworkEvent.java b/api/src/main/java/io/opencensus/trace/NetworkEvent.java new file mode 100644 index 00000000..722029e5 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/NetworkEvent.java @@ -0,0 +1,200 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Timestamp; +import io.opencensus.internal.Utils; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents a network event. It requires a {@link Type type} and a message id that + * serves to uniquely identify each network message. It can optionally can have information about + * the kernel time and message size. + * + * @deprecated Use {@link MessageEvent}. + * @since 0.5 + */ +@Immutable +@AutoValue +@AutoValue.CopyAnnotations +@Deprecated +public abstract class NetworkEvent extends io.opencensus.trace.BaseMessageEvent { + /** + * Available types for a {@code NetworkEvent}. + * + * @since 0.5 + */ + public enum Type { + /** + * When the message was sent. + * + * @since 0.5 + */ + SENT, + /** + * When the message was received. + * + * @since 0.5 + */ + RECV, + } + + /** + * Returns a new {@link Builder} with default values. + * + * @param type designates whether this is a network send or receive message. + * @param messageId serves to uniquely identify each network message. + * @return a new {@code Builder} with default values. + * @throws NullPointerException if {@code type} is {@code null}. + * @since 0.5 + */ + public static Builder builder(Type type, long messageId) { + return new AutoValue_NetworkEvent.Builder() + .setType(Utils.checkNotNull(type, "type")) + .setMessageId(messageId) + // We need to set a value for the message size because the autovalue requires all + // primitives to be initialized. + .setUncompressedMessageSize(0) + .setCompressedMessageSize(0); + } + + /** + * Returns the kernel timestamp associated with the {@code NetworkEvent} or {@code null} if not + * set. + * + * @return the kernel timestamp associated with the {@code NetworkEvent} or {@code null} if not + * set. + * @since 0.5 + */ + @Nullable + public abstract Timestamp getKernelTimestamp(); + + /** + * Returns the type of the {@code NetworkEvent}. + * + * @return the type of the {@code NetworkEvent}. + * @since 0.5 + */ + public abstract Type getType(); + + /** + * Returns the message id argument that serves to uniquely identify each network message. + * + * @return the message id of the {@code NetworkEvent}. + * @since 0.5 + */ + public abstract long getMessageId(); + + /** + * Returns the uncompressed size in bytes of the {@code NetworkEvent}. + * + * @return the uncompressed size in bytes of the {@code NetworkEvent}. + * @since 0.6 + */ + public abstract long getUncompressedMessageSize(); + + /** + * Returns the compressed size in bytes of the {@code NetworkEvent}. + * + * @return the compressed size in bytes of the {@code NetworkEvent}. + * @since 0.6 + */ + public abstract long getCompressedMessageSize(); + + /** + * Returns the uncompressed size in bytes of the {@code NetworkEvent}. + * + * @deprecated Use {@link #getUncompressedMessageSize}. + * @return the uncompressed size in bytes of the {@code NetworkEvent}. + * @since 0.5 + */ + @Deprecated + public long getMessageSize() { + return getUncompressedMessageSize(); + } + + /** + * Builder class for {@link NetworkEvent}. + * + * @deprecated {@link NetworkEvent} is deprecated. Please use {@link MessageEvent} and its builder + * {@link MessageEvent.Builder}. + * @since 0.5 + */ + @AutoValue.Builder + @Deprecated + public abstract static class Builder { + // Package protected methods because these values are mandatory and set only in the + // NetworkEvent#builder() function. + abstract Builder setType(Type type); + + abstract Builder setMessageId(long messageId); + + /** + * Sets the kernel timestamp. + * + * @param kernelTimestamp The kernel timestamp of the event. + * @return this. + * @since 0.5 + */ + public abstract Builder setKernelTimestamp(@Nullable Timestamp kernelTimestamp); + + /** + * Sets the uncompressed message size. + * + * @deprecated Use {@link #setUncompressedMessageSize}. + * @param messageSize represents the uncompressed size in bytes of this message. + * @return this. + * @since 0.5 + */ + @Deprecated + public Builder setMessageSize(long messageSize) { + return setUncompressedMessageSize(messageSize); + } + + /** + * Sets the uncompressed message size. + * + * @param uncompressedMessageSize represents the uncompressed size in bytes of this message. + * @return this. + * @since 0.6 + */ + public abstract Builder setUncompressedMessageSize(long uncompressedMessageSize); + + /** + * Sets the compressed message size. + * + * @param compressedMessageSize represents the compressed size in bytes of this message. + * @return this. + * @since 0.6 + */ + public abstract Builder setCompressedMessageSize(long compressedMessageSize); + + /** + * Builds and returns a {@code NetworkEvent} with the desired values. + * + * @return a {@code NetworkEvent} with the desired values. + * @since 0.5 + */ + public abstract NetworkEvent build(); + + Builder() {} + } + + NetworkEvent() {} +} diff --git a/api/src/main/java/io/opencensus/trace/Sampler.java b/api/src/main/java/io/opencensus/trace/Sampler.java new file mode 100644 index 00000000..e89af89b --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Sampler.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import java.util.List; +import javax.annotation.Nullable; + +/** + * Sampler is used to make decisions on {@link Span} sampling. + * + * @since 0.5 + */ +public abstract class Sampler { + /** + * Called during {@link Span} creation to make a sampling decision. + * + * @param parentContext the parent span's {@link SpanContext}. {@code null} if this is a root + * span. + * @param hasRemoteParent {@code true} if the parent {@code Span} is remote. {@code null} if this + * is a root span. + * @param traceId the {@link TraceId} for the new {@code Span}. This will be identical to that in + * the parentContext, unless this is a root span. + * @param spanId the {@link SpanId} for the new {@code Span}. + * @param name the name of the new {@code Span}. + * @param parentLinks the parentLinks associated with the new {@code Span}. + * @return {@code true} if the {@code Span} is sampled. + * @since 0.5 + */ + public abstract boolean shouldSample( + @Nullable SpanContext parentContext, + @Nullable Boolean hasRemoteParent, + TraceId traceId, + SpanId spanId, + String name, + List<Span> parentLinks); + + /** + * Returns the description of this {@code Sampler}. This may be displayed on debug pages or in the + * logs. + * + * <p>Example: "ProbabilitySampler{0.000100}" + * + * @return the description of this {@code Sampler}. + * @since 0.6 + */ + public abstract String getDescription(); +} diff --git a/api/src/main/java/io/opencensus/trace/Span.java b/api/src/main/java/io/opencensus/trace/Span.java new file mode 100644 index 00000000..8f8253b4 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Span.java @@ -0,0 +1,288 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.internal.Utils; +import io.opencensus.trace.internal.BaseMessageEventUtils; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; + +/** + * An abstract class that represents a span. It has an associated {@link SpanContext} and a set of + * {@link Options}. + * + * <p>Spans are created by the {@link SpanBuilder#startSpan} method. + * + * <p>{@code Span} <b>must</b> be ended by calling {@link #end()} or {@link #end(EndSpanOptions)} + * + * @since 0.5 + */ +public abstract class Span { + private static final Map<String, AttributeValue> EMPTY_ATTRIBUTES = Collections.emptyMap(); + + // Contains the identifiers associated with this Span. + private final SpanContext context; + + // Contains the options associated with this Span. This object is immutable. + private final Set<Options> options; + + /** + * {@code Span} options. These options are NOT propagated to child spans. These options determine + * features such as whether a {@code Span} should record any annotations or events. + * + * @since 0.5 + */ + public enum Options { + /** + * This option is set if the Span is part of a sampled distributed trace OR {@link + * SpanBuilder#setRecordEvents(boolean)} was called with true. + * + * @since 0.5 + */ + RECORD_EVENTS; + } + + private static final Set<Options> DEFAULT_OPTIONS = + Collections.unmodifiableSet(EnumSet.noneOf(Options.class)); + + /** + * Creates a new {@code Span}. + * + * @param context the context associated with this {@code Span}. + * @param options the options associated with this {@code Span}. If {@code null} then default + * options will be set. + * @throws NullPointerException if context is {@code null}. + * @throws IllegalArgumentException if the {@code SpanContext} is sampled but no RECORD_EVENTS + * options. + * @since 0.5 + */ + protected Span(SpanContext context, @Nullable EnumSet<Options> options) { + this.context = Utils.checkNotNull(context, "context"); + this.options = + options == null + ? DEFAULT_OPTIONS + : Collections.<Options>unmodifiableSet(EnumSet.copyOf(options)); + Utils.checkArgument( + !context.getTraceOptions().isSampled() || (this.options.contains(Options.RECORD_EVENTS)), + "Span is sampled, but does not have RECORD_EVENTS set."); + } + + /** + * Sets an attribute to the {@code Span}. If the {@code Span} previously contained a mapping for + * the key, the old value is replaced by the specified value. + * + * @param key the key for this attribute. + * @param value the value for this attribute. + * @since 0.6 + */ + public void putAttribute(String key, AttributeValue value) { + // Not final because for performance reasons we want to override this in the implementation. + // Also a default implementation is needed to not break the compatibility (users may extend this + // for testing). + Utils.checkNotNull(key, "key"); + Utils.checkNotNull(value, "value"); + putAttributes(Collections.singletonMap(key, value)); + } + + /** + * Sets a set of attributes to the {@code Span}. The effect of this call is equivalent to that of + * calling {@link #putAttribute(String, AttributeValue)} once for each element in the specified + * map. + * + * @param attributes the attributes that will be added and associated with the {@code Span}. + * @since 0.6 + */ + public void putAttributes(Map<String, AttributeValue> attributes) { + // Not final because we want to start overriding this method from the beginning, this will + // allow us to remove the addAttributes faster. All implementations MUST override this method. + Utils.checkNotNull(attributes, "attributes"); + addAttributes(attributes); + } + + /** + * Sets a set of attributes to the {@code Span}. The effect of this call is equivalent to that of + * calling {@link #putAttribute(String, AttributeValue)} once for each element in the specified + * map. + * + * @deprecated Use {@link #putAttributes(Map)} + * @param attributes the attributes that will be added and associated with the {@code Span}. + * @since 0.5 + */ + @Deprecated + public void addAttributes(Map<String, AttributeValue> attributes) { + putAttributes(attributes); + } + + /** + * Adds an annotation to the {@code Span}. + * + * @param description the description of the annotation time event. + * @since 0.5 + */ + public final void addAnnotation(String description) { + Utils.checkNotNull(description, "description"); + addAnnotation(description, EMPTY_ATTRIBUTES); + } + + /** + * Adds an annotation to the {@code Span}. + * + * @param description the description of the annotation time event. + * @param attributes the attributes that will be added; these are associated with this annotation, + * not the {@code Span} as for {@link #putAttributes(Map)}. + * @since 0.5 + */ + public abstract void addAnnotation(String description, Map<String, AttributeValue> attributes); + + /** + * Adds an annotation to the {@code Span}. + * + * @param annotation the annotations to add. + * @since 0.5 + */ + public abstract void addAnnotation(Annotation annotation); + + /** + * Adds a NetworkEvent to the {@code Span}. + * + * <p>This function is only intended to be used by RPC systems (either client or server), not by + * higher level applications. + * + * @param networkEvent the network event to add. + * @deprecated Use {@link #addMessageEvent}. + * @since 0.5 + */ + @Deprecated + public void addNetworkEvent(NetworkEvent networkEvent) { + addMessageEvent(BaseMessageEventUtils.asMessageEvent(networkEvent)); + } + + /** + * Adds a MessageEvent to the {@code Span}. + * + * <p>This function can be used by higher level applications to record messaging event. + * + * <p>This method should always be overridden by users whose API versions are larger or equal to + * {@code 0.12}. + * + * @param messageEvent the message to add. + * @since 0.12 + */ + public void addMessageEvent(MessageEvent messageEvent) { + // Default implementation by invoking addNetworkEvent() so that any existing derived classes, + // including implementation and the mocked ones, do not need to override this method explicitly. + Utils.checkNotNull(messageEvent, "messageEvent"); + addNetworkEvent(BaseMessageEventUtils.asNetworkEvent(messageEvent)); + } + + /** + * Adds a {@link Link} to the {@code Span}. + * + * <p>Used (for example) in batching operations, where a single batch handler processes multiple + * requests from different traces. + * + * @param link the link to add. + * @since 0.5 + */ + public abstract void addLink(Link link); + + /** + * Sets the {@link Status} to the {@code Span}. + * + * <p>If used, this will override the default {@code Span} status. Default is {@link Status#OK}. + * + * <p>Only the value of the last call will be recorded, and implementations are free to ignore + * previous calls. If the status is set via {@link EndSpanOptions.Builder#setStatus(Status)} that + * will always be the last call. + * + * @param status the {@link Status} to set. + * @since 0.9 + */ + public void setStatus(Status status) { + // Implemented as no-op for backwards compatibility (for example gRPC extends Span in tests). + // Implementation must override this method. + Utils.checkNotNull(status, "status"); + } + + /** + * Marks the end of {@code Span} execution with the given options. + * + * <p>Only the timing of the first end call for a given {@code Span} will be recorded, and + * implementations are free to ignore all further calls. + * + * @param options the options to be used for the end of the {@code Span}. + * @since 0.5 + */ + public abstract void end(EndSpanOptions options); + + /** + * Marks the end of {@code Span} execution with the default options. + * + * <p>Only the timing of the first end call for a given {@code Span} will be recorded, and + * implementations are free to ignore all further calls. + * + * @since 0.5 + */ + public final void end() { + end(EndSpanOptions.DEFAULT); + } + + /** + * Returns the {@code SpanContext} associated with this {@code Span}. + * + * @return the {@code SpanContext} associated with this {@code Span}. + * @since 0.5 + */ + public final SpanContext getContext() { + return context; + } + + /** + * Returns the options associated with this {@code Span}. + * + * @return the options associated with this {@code Span}. + * @since 0.5 + */ + public final Set<Options> getOptions() { + return options; + } + + /** + * Type of span. Can be used to specify additional relationships between spans in addition to a + * parent/child relationship. + * + * @since 0.14 + */ + public enum Kind { + /** + * Indicates that the span covers server-side handling of an RPC or other remote request. + * + * @since 0.14 + */ + SERVER, + + /** + * Indicates that the span covers the client-side wrapper around an RPC or other remote request. + * + * @since 0.14 + */ + CLIENT + } +} diff --git a/api/src/main/java/io/opencensus/trace/SpanBuilder.java b/api/src/main/java/io/opencensus/trace/SpanBuilder.java new file mode 100644 index 00000000..f3a436a6 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanBuilder.java @@ -0,0 +1,356 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.errorprone.annotations.MustBeClosed; +import io.opencensus.common.Scope; +import io.opencensus.internal.Utils; +import java.util.List; +import java.util.concurrent.Callable; +import javax.annotation.Nullable; + +/** + * {@link SpanBuilder} is used to construct {@link Span} instances which define arbitrary scopes of + * code that are sampled for distributed tracing as a single atomic unit. + * + * <p>This is a simple example where all the work is being done within a single scope and a single + * thread and the Context is automatically propagated: + * + * <pre>{@code + * class MyClass { + * private static final Tracer tracer = Tracing.getTracer(); + * void doWork { + * // Create a Span as a child of the current Span. + * try (Scope ss = tracer.spanBuilder("MyChildSpan").startScopedSpan()) { + * tracer.getCurrentSpan().addAnnotation("my annotation"); + * doSomeWork(); // Here the new span is in the current Context, so it can be used + * // implicitly anywhere down the stack. + * } + * } + * } + * }</pre> + * + * <p>There might be cases where you do not perform all the work inside one static scope and the + * Context is automatically propagated: + * + * <pre>{@code + * class MyRpcServerInterceptorListener implements RpcServerInterceptor.Listener { + * private static final Tracer tracer = Tracing.getTracer(); + * private Span mySpan; + * + * public MyRpcInterceptor() {} + * + * public void onRequest(String rpcName, Metadata metadata) { + * // Create a Span as a child of the remote Span. + * mySpan = tracer.spanBuilderWithRemoteParent( + * getTraceContextFromMetadata(metadata), rpcName).startSpan(); + * } + * + * public void onExecuteHandler(ServerCallHandler serverCallHandler) { + * try (Scope ws = tracer.withSpan(mySpan)) { + * tracer.getCurrentSpan().addAnnotation("Start rpc execution."); + * serverCallHandler.run(); // Here the new span is in the current Context, so it can be + * // used implicitly anywhere down the stack. + * } + * } + * + * // Called when the RPC is canceled and guaranteed onComplete will not be called. + * public void onCancel() { + * // IMPORTANT: DO NOT forget to ended the Span here as the work is done. + * mySpan.end(EndSpanOptions.builder().setStatus(Status.CANCELLED)); + * } + * + * // Called when the RPC is done and guaranteed onCancel will not be called. + * public void onComplete(RpcStatus rpcStatus) { + * // IMPORTANT: DO NOT forget to ended the Span here as the work is done. + * mySpan.end(EndSpanOptions.builder().setStatus(rpcStatusToCanonicalTraceStatus(status)); + * } + * } + * }</pre> + * + * <p>This is a simple example where all the work is being done within a single scope and the + * Context is manually propagated: + * + * <pre>{@code + * class MyClass { + * private static final Tracer tracer = Tracing.getTracer(); + * void DoWork(Span parent) { + * Span childSpan = tracer.spanBuilderWithExplicitParent("MyChildSpan", parent).startSpan(); + * childSpan.addAnnotation("my annotation"); + * try { + * doSomeWork(childSpan); // Manually propagate the new span down the stack. + * } finally { + * // To make sure we end the span even in case of an exception. + * childSpan.end(); // Manually end the span. + * } + * } + * } + * }</pre> + * + * <p>If your Java version is less than Java SE 7, see {@link SpanBuilder#startSpan} and {@link + * SpanBuilder#startScopedSpan} for usage examples. + * + * @since 0.5 + */ +public abstract class SpanBuilder { + + /** + * Sets the {@link Sampler} to use. If not set, the implementation will provide a default. + * + * @param sampler the {@code Sampler} to use when determining sampling for a {@code Span}. + * @return this. + * @since 0.5 + */ + public abstract SpanBuilder setSampler(Sampler sampler); + + /** + * Sets the {@code List} of parent links. Links are used to link {@link Span}s in different + * traces. Used (for example) in batching operations, where a single batch handler processes + * multiple requests from different traces. + * + * @param parentLinks new links to be added. + * @return this. + * @throws NullPointerException if {@code parentLinks} is {@code null}. + * @since 0.5 + */ + public abstract SpanBuilder setParentLinks(List<Span> parentLinks); + + /** + * Sets the option {@link Span.Options#RECORD_EVENTS} for the newly created {@code Span}. If not + * called, the implementation will provide a default. + * + * @param recordEvents new value determining if this {@code Span} should have events recorded. + * @return this. + * @since 0.5 + */ + public abstract SpanBuilder setRecordEvents(boolean recordEvents); + + /** + * Sets the {@link Span.Kind} for the newly created {@code Span}. If not called, the + * implementation will provide a default. + * + * @param spanKind the kind of the newly created {@code Span}. + * @return this. + * @since 0.14 + */ + public SpanBuilder setSpanKind(@Nullable Span.Kind spanKind) { + return this; + } + + /** + * Starts a new {@link Span}. + * + * <p>Users <b>must</b> manually call {@link Span#end()} or {@link Span#end(EndSpanOptions)} to + * end this {@code Span}. + * + * <p>Does not install the newly created {@code Span} to the current Context. + * + * <p>Example of usage: + * + * <pre>{@code + * class MyClass { + * private static final Tracer tracer = Tracing.getTracer(); + * void DoWork(Span parent) { + * Span childSpan = tracer.spanBuilderWithExplicitParent("MyChildSpan", parent).startSpan(); + * childSpan.addAnnotation("my annotation"); + * try { + * doSomeWork(childSpan); // Manually propagate the new span down the stack. + * } finally { + * // To make sure we end the span even in case of an exception. + * childSpan.end(); // Manually end the span. + * } + * } + * } + * }</pre> + * + * @return the newly created {@code Span}. + * @since 0.5 + */ + public abstract Span startSpan(); + + /** + * Starts a new span and sets it as the {@link Tracer#getCurrentSpan current span}. + * + * <p>Enters the scope of code where the newly created {@code Span} is in the current Context, and + * returns an object that represents that scope. When the returned object is closed, the scope is + * exited, the previous Context is restored, and the newly created {@code Span} is ended using + * {@link Span#end}. + * + * <p>Supports try-with-resource idiom. + * + * <p>Example of usage: + * + * <pre>{@code + * class MyClass { + * private static final Tracer tracer = Tracing.getTracer(); + * void doWork { + * // Create a Span as a child of the current Span. + * try (Scope ss = tracer.spanBuilder("MyChildSpan").startScopedSpan()) { + * tracer.getCurrentSpan().addAnnotation("my annotation"); + * doSomeWork(); // Here the new span is in the current Context, so it can be used + * // implicitly anywhere down the stack. Anytime in this closure the span + * // can be accessed via tracer.getCurrentSpan(). + * } + * } + * } + * }</pre> + * + * <p>Prior to Java SE 7, you can use a finally block to ensure that a resource is closed (the + * {@code Span} is ended and removed from the Context) regardless of whether the try statement + * completes normally or abruptly. + * + * <p>Example of usage prior to Java SE7: + * + * <pre>{@code + * class MyClass { + * private static Tracer tracer = Tracing.getTracer(); + * void doWork { + * // Create a Span as a child of the current Span. + * Scope ss = tracer.spanBuilder("MyChildSpan").startScopedSpan(); + * try { + * tracer.getCurrentSpan().addAnnotation("my annotation"); + * doSomeWork(); // Here the new span is in the current Context, so it can be used + * // implicitly anywhere down the stack. Anytime in this closure the span + * // can be accessed via tracer.getCurrentSpan(). + * } finally { + * ss.close(); + * } + * } + * } + * }</pre> + * + * <p>WARNING: The try-with-resources feature to auto-close spans as described above can sound + * very tempting due to its convenience, but it comes with an important and easy-to-miss + * trade-off: the span will be closed before any {@code catch} or {@code finally} blocks get a + * chance to execute. So if you need to catch any exceptions and log information about them (for + * example), then you do not want to use the try-with-resources shortcut because that logging will + * not be tagged with the span info of the span it logically falls under, and if you try to + * retrieve {@code Tracer.getCurrentSpan()} then you'll either get the parent span if one exists + * or {@code BlankSpan} if there was no parent span. This can be confusing and seem + * counter-intuitive, but it's the way try-with-resources works. + * + * @return an object that defines a scope where the newly created {@code Span} will be set to the + * current Context. + * @since 0.5 + */ + @MustBeClosed + public final Scope startScopedSpan() { + return CurrentSpanUtils.withSpan(startSpan(), /* endSpan= */ true); + } + + /** + * Starts a new span and runs the given {@code Runnable} with the newly created {@code Span} as + * the current {@code Span}, and ends the {@code Span} after the {@code Runnable} is run. + * + * <p>Any error will end up as a {@link Status#UNKNOWN}. + * + * <pre><code> + * tracer.spanBuilder("MyRunnableSpan").startSpanAndRun(myRunnable); + * </code></pre> + * + * <p>It is equivalent with the following code: + * + * <pre><code> + * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan(); + * Runnable newRunnable = tracer.withSpan(span, myRunnable); + * try { + * newRunnable.run(); + * } finally { + * span.end(); + * } + * </code></pre> + * + * @param runnable the {@code Runnable} to run in the {@code Span}. + * @since 0.11.0 + */ + public final void startSpanAndRun(final Runnable runnable) { + final Span span = startSpan(); + CurrentSpanUtils.withSpan(span, /* endSpan= */ true, runnable).run(); + } + + /** + * Starts a new span and calls the given {@code Callable} with the newly created {@code Span} as + * the current {@code Span}, and ends the {@code Span} after the {@code Callable} is called. + * + * <p>Any error will end up as a {@link Status#UNKNOWN}. + * + * <pre><code> + * MyResult myResult = tracer.spanBuilder("MyCallableSpan").startSpanAndCall(myCallable); + * </code></pre> + * + * <p>It is equivalent with the following code: + * + * <pre><code> + * Span span = tracer.spanBuilder("MyCallableSpan").startSpan(); + * {@code Callable<MyResult>} newCallable = tracer.withSpan(span, myCallable); + * MyResult myResult = null; + * try { + * myResult = newCallable.call(); + * } finally { + * span.end(); + * } + * ); + * </code></pre> + * + * @param callable the {@code Callable} to run in the {@code Span}. + * @since 0.11.0 + */ + public final <V> V startSpanAndCall(Callable<V> callable) throws Exception { + final Span span = startSpan(); + return CurrentSpanUtils.withSpan(span, /* endSpan= */ true, callable).call(); + } + + static final class NoopSpanBuilder extends SpanBuilder { + static NoopSpanBuilder createWithParent(String spanName, @Nullable Span parent) { + return new NoopSpanBuilder(spanName); + } + + static NoopSpanBuilder createWithRemoteParent( + String spanName, @Nullable SpanContext remoteParentSpanContext) { + return new NoopSpanBuilder(spanName); + } + + @Override + public Span startSpan() { + return BlankSpan.INSTANCE; + } + + @Override + public SpanBuilder setSampler(@Nullable Sampler sampler) { + return this; + } + + @Override + public SpanBuilder setParentLinks(List<Span> parentLinks) { + return this; + } + + @Override + public SpanBuilder setRecordEvents(boolean recordEvents) { + return this; + } + + @Override + public SpanBuilder setSpanKind(@Nullable Span.Kind spanKind) { + return this; + } + + private NoopSpanBuilder(String name) { + Utils.checkNotNull(name, "name"); + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/SpanContext.java b/api/src/main/java/io/opencensus/trace/SpanContext.java new file mode 100644 index 00000000..49ed751b --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanContext.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import java.util.Arrays; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents a span context. A span context contains the state that must propagate to + * child {@link Span}s and across process boundaries. It contains the identifiers (a {@link TraceId + * trace_id} and {@link SpanId span_id}) associated with the {@link Span} and a set of {@link + * TraceOptions options}. + * + * @since 0.5 + */ +@Immutable +public final class SpanContext { + private static final Tracestate TRACESTATE_DEFAULT = Tracestate.builder().build(); + private final TraceId traceId; + private final SpanId spanId; + private final TraceOptions traceOptions; + private final Tracestate tracestate; + + /** + * The invalid {@code SpanContext}. + * + * @since 0.5 + */ + public static final SpanContext INVALID = + new SpanContext(TraceId.INVALID, SpanId.INVALID, TraceOptions.DEFAULT, TRACESTATE_DEFAULT); + + /** + * Creates a new {@code SpanContext} with the given identifiers and options. + * + * @param traceId the trace identifier of the span context. + * @param spanId the span identifier of the span context. + * @param traceOptions the trace options for the span context. + * @return a new {@code SpanContext} with the given identifiers and options. + * @deprecated use {@link #create(TraceId, SpanId, TraceOptions, Tracestate)}. + */ + @Deprecated + public static SpanContext create(TraceId traceId, SpanId spanId, TraceOptions traceOptions) { + return create(traceId, spanId, traceOptions, TRACESTATE_DEFAULT); + } + + /** + * Creates a new {@code SpanContext} with the given identifiers and options. + * + * @param traceId the trace identifier of the span context. + * @param spanId the span identifier of the span context. + * @param traceOptions the trace options for the span context. + * @param tracestate the trace state for the span context. + * @return a new {@code SpanContext} with the given identifiers and options. + * @since 0.16 + */ + public static SpanContext create( + TraceId traceId, SpanId spanId, TraceOptions traceOptions, Tracestate tracestate) { + return new SpanContext(traceId, spanId, traceOptions, tracestate); + } + + /** + * Returns the trace identifier associated with this {@code SpanContext}. + * + * @return the trace identifier associated with this {@code SpanContext}. + * @since 0.5 + */ + public TraceId getTraceId() { + return traceId; + } + + /** + * Returns the span identifier associated with this {@code SpanContext}. + * + * @return the span identifier associated with this {@code SpanContext}. + * @since 0.5 + */ + public SpanId getSpanId() { + return spanId; + } + + /** + * Returns the {@code TraceOptions} associated with this {@code SpanContext}. + * + * @return the {@code TraceOptions} associated with this {@code SpanContext}. + * @since 0.5 + */ + public TraceOptions getTraceOptions() { + return traceOptions; + } + + /** + * Returns the {@code Tracestate} associated with this {@code SpanContext}. + * + * @return the {@code Tracestate} associated with this {@code SpanContext}. + * @since 0.5 + */ + public Tracestate getTracestate() { + return tracestate; + } + + /** + * Returns true if this {@code SpanContext} is valid. + * + * @return true if this {@code SpanContext} is valid. + * @since 0.5 + */ + public boolean isValid() { + return traceId.isValid() && spanId.isValid(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof SpanContext)) { + return false; + } + + SpanContext that = (SpanContext) obj; + return traceId.equals(that.traceId) + && spanId.equals(that.spanId) + && traceOptions.equals(that.traceOptions); + } + + @Override + public int hashCode() { + return Arrays.hashCode(new Object[] {traceId, spanId, traceOptions}); + } + + @Override + public String toString() { + return "SpanContext{traceId=" + + traceId + + ", spanId=" + + spanId + + ", traceOptions=" + + traceOptions + + "}"; + } + + private SpanContext( + TraceId traceId, SpanId spanId, TraceOptions traceOptions, Tracestate tracestate) { + this.traceId = traceId; + this.spanId = spanId; + this.traceOptions = traceOptions; + this.tracestate = tracestate; + } +} diff --git a/api/src/main/java/io/opencensus/trace/SpanId.java b/api/src/main/java/io/opencensus/trace/SpanId.java new file mode 100644 index 00000000..c43fa6b0 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/SpanId.java @@ -0,0 +1,214 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.internal.Utils; +import java.util.Arrays; +import java.util.Random; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents a span identifier. A valid span identifier is an 8-byte array with at + * least one non-zero byte. + * + * @since 0.5 + */ +@Immutable +public final class SpanId implements Comparable<SpanId> { + /** + * The size in bytes of the {@code SpanId}. + * + * @since 0.5 + */ + public static final int SIZE = 8; + + private static final int HEX_SIZE = 2 * SIZE; + + /** + * The invalid {@code SpanId}. All bytes are 0. + * + * @since 0.5 + */ + public static final SpanId INVALID = new SpanId(new byte[SIZE]); + + // The internal representation of the SpanId. + private final byte[] bytes; + + private SpanId(byte[] bytes) { + this.bytes = bytes; + } + + /** + * Returns a {@code SpanId} built from a byte representation. + * + * <p>Equivalent with: + * + * <pre>{@code + * SpanId.fromBytes(buffer, 0); + * }</pre> + * + * @param buffer the representation of the {@code SpanId}. + * @return a {@code SpanId} whose representation is given by the {@code buffer} parameter. + * @throws NullPointerException if {@code buffer} is null. + * @throws IllegalArgumentException if {@code buffer.length} is not {@link SpanId#SIZE}. + * @since 0.5 + */ + public static SpanId fromBytes(byte[] buffer) { + Utils.checkNotNull(buffer, "buffer"); + Utils.checkArgument( + buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length); + byte[] bytesCopied = Arrays.copyOf(buffer, SIZE); + return new SpanId(bytesCopied); + } + + /** + * Returns a {@code SpanId} whose representation is copied from the {@code src} beginning at the + * {@code srcOffset} offset. + * + * @param src the buffer where the representation of the {@code SpanId} is copied. + * @param srcOffset the offset in the buffer where the representation of the {@code SpanId} + * begins. + * @return a {@code SpanId} whose representation is copied from the buffer. + * @throws NullPointerException if {@code src} is null. + * @throws IndexOutOfBoundsException if {@code srcOffset+SpanId.SIZE} is greater than {@code + * src.length}. + * @since 0.5 + */ + public static SpanId fromBytes(byte[] src, int srcOffset) { + byte[] bytes = new byte[SIZE]; + System.arraycopy(src, srcOffset, bytes, 0, SIZE); + return new SpanId(bytes); + } + + /** + * Returns a {@code SpanId} built from a lowercase base16 representation. + * + * @param src the lowercase base16 representation. + * @return a {@code SpanId} built from a lowercase base16 representation. + * @throws NullPointerException if {@code src} is null. + * @throws IllegalArgumentException if {@code src.length} is not {@code 2 * SpanId.SIZE} OR if the + * {@code str} has invalid characters. + * @since 0.11 + */ + public static SpanId fromLowerBase16(CharSequence src) { + Utils.checkArgument( + src.length() == HEX_SIZE, "Invalid size: expected %s, got %s", HEX_SIZE, src.length()); + return new SpanId(LowerCaseBase16Encoding.decodeToBytes(src)); + } + + /** + * Generates a new random {@code SpanId}. + * + * @param random The random number generator. + * @return a valid new {@code SpanId}. + * @since 0.5 + */ + public static SpanId generateRandomId(Random random) { + byte[] bytes = new byte[SIZE]; + do { + random.nextBytes(bytes); + } while (Arrays.equals(bytes, INVALID.bytes)); + return new SpanId(bytes); + } + + /** + * Returns the byte representation of the {@code SpanId}. + * + * @return the byte representation of the {@code SpanId}. + * @since 0.5 + */ + public byte[] getBytes() { + return Arrays.copyOf(bytes, SIZE); + } + + /** + * Copies the byte array representations of the {@code SpanId} into the {@code dest} beginning at + * the {@code destOffset} offset. + * + * <p>Equivalent with (but faster because it avoids any new allocations): + * + * <pre>{@code + * System.arraycopy(getBytes(), 0, dest, destOffset, SpanId.SIZE); + * }</pre> + * + * @param dest the destination buffer. + * @param destOffset the starting offset in the destination buffer. + * @throws NullPointerException if {@code dest} is null. + * @throws IndexOutOfBoundsException if {@code destOffset+SpanId.SIZE} is greater than {@code + * dest.length}. + * @since 0.5 + */ + public void copyBytesTo(byte[] dest, int destOffset) { + System.arraycopy(bytes, 0, dest, destOffset, SIZE); + } + + /** + * Returns whether the span identifier is valid. A valid span identifier is an 8-byte array with + * at least one non-zero byte. + * + * @return {@code true} if the span identifier is valid. + * @since 0.5 + */ + public boolean isValid() { + return !Arrays.equals(bytes, INVALID.bytes); + } + + /** + * Returns the lowercase base16 encoding of this {@code SpanId}. + * + * @return the lowercase base16 encoding of this {@code SpanId}. + * @since 0.11 + */ + public String toLowerBase16() { + return LowerCaseBase16Encoding.encodeToString(bytes); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof SpanId)) { + return false; + } + + SpanId that = (SpanId) obj; + return Arrays.equals(bytes, that.bytes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(bytes); + } + + @Override + public String toString() { + return "SpanId{spanId=" + toLowerBase16() + "}"; + } + + @Override + public int compareTo(SpanId that) { + for (int i = 0; i < SIZE; i++) { + if (bytes[i] != that.bytes[i]) { + return bytes[i] < that.bytes[i] ? -1 : 1; + } + } + return 0; + } +} diff --git a/api/src/main/java/io/opencensus/trace/Status.java b/api/src/main/java/io/opencensus/trace/Status.java new file mode 100644 index 00000000..1fa85085 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Status.java @@ -0,0 +1,469 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.internal.Utils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.TreeMap; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/*>>> +import org.checkerframework.dataflow.qual.Deterministic; +*/ + +/** + * Defines the status of a {@link Span} by providing a standard {@link CanonicalCode} in conjunction + * with an optional descriptive message. Instances of {@code Status} are created by starting with + * the template for the appropriate {@link Status.CanonicalCode} and supplementing it with + * additional information: {@code Status.NOT_FOUND.withDescription("Could not find + * 'important_file.txt'");} + * + * @since 0.5 + */ +@Immutable +public final class Status { + /** + * The set of canonical status codes. If new codes are added over time they must choose a + * numerical value that does not collide with any previously used value. + * + * @since 0.5 + */ + public enum CanonicalCode { + /** + * The operation completed successfully. + * + * @since 0.5 + */ + OK(0), + + /** + * The operation was cancelled (typically by the caller). + * + * @since 0.5 + */ + CANCELLED(1), + + /** + * Unknown error. An example of where this error may be returned is if a Status value received + * from another address space belongs to an error-space that is not known in this address space. + * Also errors raised by APIs that do not return enough error information may be converted to + * this error. + * + * @since 0.5 + */ + UNKNOWN(2), + + /** + * Client specified an invalid argument. Note that this differs from FAILED_PRECONDITION. + * INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the + * system (e.g., a malformed file name). + * + * @since 0.5 + */ + INVALID_ARGUMENT(3), + + /** + * Deadline expired before operation could complete. For operations that change the state of the + * system, this error may be returned even if the operation has completed successfully. For + * example, a successful response from a server could have been delayed long enough for the + * deadline to expire. + * + * @since 0.5 + */ + DEADLINE_EXCEEDED(4), + + /** + * Some requested entity (e.g., file or directory) was not found. + * + * @since 0.5 + */ + NOT_FOUND(5), + + /** + * Some entity that we attempted to create (e.g., file or directory) already exists. + * + * @since 0.5 + */ + ALREADY_EXISTS(6), + + /** + * The caller does not have permission to execute the specified operation. PERMISSION_DENIED + * must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED + * instead for those errors). PERMISSION_DENIED must not be used if the caller cannot be + * identified (use UNAUTHENTICATED instead for those errors). + * + * @since 0.5 + */ + PERMISSION_DENIED(7), + + /** + * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system + * is out of space. + * + * @since 0.5 + */ + RESOURCE_EXHAUSTED(8), + + /** + * Operation was rejected because the system is not in a state required for the operation's + * execution. For example, directory to be deleted may be non-empty, an rmdir operation is + * applied to a non-directory, etc. + * + * <p>A litmus test that may help a service implementor in deciding between FAILED_PRECONDITION, + * ABORTED, and UNAVAILABLE: (a) Use UNAVAILABLE if the client can retry just the failing call. + * (b) Use ABORTED if the client should retry at a higher-level (e.g., restarting a + * read-modify-write sequence). (c) Use FAILED_PRECONDITION if the client should not retry until + * the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory + * is non-empty, FAILED_PRECONDITION should be returned since the client should not retry unless + * they have first fixed up the directory by deleting files from it. + * + * @since 0.5 + */ + FAILED_PRECONDITION(9), + + /** + * The operation was aborted, typically due to a concurrency issue like sequencer check + * failures, transaction aborts, etc. + * + * <p>See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE. + * + * @since 0.5 + */ + ABORTED(10), + + /** + * Operation was attempted past the valid range. E.g., seeking or reading past end of file. + * + * <p>Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system + * state changes. For example, a 32-bit file system will generate INVALID_ARGUMENT if asked to + * read at an offset that is not in the range [0,2^32-1], but it will generate OUT_OF_RANGE if + * asked to read from an offset past the current file size. + * + * <p>There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE. We recommend + * using OUT_OF_RANGE (the more specific error) when it applies so that callers who are + * iterating through a space can easily look for an OUT_OF_RANGE error to detect when they are + * done. + * + * @since 0.5 + */ + OUT_OF_RANGE(11), + + /** + * Operation is not implemented or not supported/enabled in this service. + * + * @since 0.5 + */ + UNIMPLEMENTED(12), + + /** + * Internal errors. Means some invariants expected by underlying system has been broken. If you + * see one of these errors, something is very broken. + * + * @since 0.5 + */ + INTERNAL(13), + + /** + * The service is currently unavailable. This is a most likely a transient condition and may be + * corrected by retrying with a backoff. + * + * <p>See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE. + * + * @since 0.5 + */ + UNAVAILABLE(14), + + /** + * Unrecoverable data loss or corruption. + * + * @since 0.5 + */ + DATA_LOSS(15), + + /** + * The request does not have valid authentication credentials for the operation. + * + * @since 0.5 + */ + UNAUTHENTICATED(16); + + private final int value; + + private CanonicalCode(int value) { + this.value = value; + } + + /** + * Returns the numerical value of the code. + * + * @return the numerical value of the code. + * @since 0.5 + */ + public int value() { + return value; + } + + /** + * Returns the status that has the current {@code CanonicalCode}.. + * + * @return the status that has the current {@code CanonicalCode}. + * @since 0.5 + */ + public Status toStatus() { + return STATUS_LIST.get(value); + } + } + + // Create the canonical list of Status instances indexed by their code values. + private static final List<Status> STATUS_LIST = buildStatusList(); + + private static List<Status> buildStatusList() { + TreeMap<Integer, Status> canonicalizer = new TreeMap<Integer, Status>(); + for (CanonicalCode code : CanonicalCode.values()) { + Status replaced = canonicalizer.put(code.value(), new Status(code, null)); + if (replaced != null) { + throw new IllegalStateException( + "Code value duplication between " + + replaced.getCanonicalCode().name() + + " & " + + code.name()); + } + } + return Collections.unmodifiableList(new ArrayList<Status>(canonicalizer.values())); + } + + // A pseudo-enum of Status instances mapped 1:1 with values in CanonicalCode. This simplifies + // construction patterns for derived instances of Status. + /** + * The operation completed successfully. + * + * @since 0.5 + */ + public static final Status OK = CanonicalCode.OK.toStatus(); + + /** + * The operation was cancelled (typically by the caller). + * + * @since 0.5 + */ + public static final Status CANCELLED = CanonicalCode.CANCELLED.toStatus(); + + /** + * Unknown error. See {@link CanonicalCode#UNKNOWN}. + * + * @since 0.5 + */ + public static final Status UNKNOWN = CanonicalCode.UNKNOWN.toStatus(); + + /** + * Client specified an invalid argument. See {@link CanonicalCode#INVALID_ARGUMENT}. + * + * @since 0.5 + */ + public static final Status INVALID_ARGUMENT = CanonicalCode.INVALID_ARGUMENT.toStatus(); + + /** + * Deadline expired before operation could complete. See {@link CanonicalCode#DEADLINE_EXCEEDED}. + * + * @since 0.5 + */ + public static final Status DEADLINE_EXCEEDED = CanonicalCode.DEADLINE_EXCEEDED.toStatus(); + + /** + * Some requested entity (e.g., file or directory) was not found. + * + * @since 0.5 + */ + public static final Status NOT_FOUND = CanonicalCode.NOT_FOUND.toStatus(); + + /** + * Some entity that we attempted to create (e.g., file or directory) already exists. + * + * @since 0.5 + */ + public static final Status ALREADY_EXISTS = CanonicalCode.ALREADY_EXISTS.toStatus(); + + /** + * The caller does not have permission to execute the specified operation. See {@link + * CanonicalCode#PERMISSION_DENIED}. + * + * @since 0.5 + */ + public static final Status PERMISSION_DENIED = CanonicalCode.PERMISSION_DENIED.toStatus(); + + /** + * The request does not have valid authentication credentials for the operation. + * + * @since 0.5 + */ + public static final Status UNAUTHENTICATED = CanonicalCode.UNAUTHENTICATED.toStatus(); + + /** + * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system + * is out of space. + * + * @since 0.5 + */ + public static final Status RESOURCE_EXHAUSTED = CanonicalCode.RESOURCE_EXHAUSTED.toStatus(); + + /** + * Operation was rejected because the system is not in a state required for the operation's + * execution. See {@link CanonicalCode#FAILED_PRECONDITION}. + * + * @since 0.5 + */ + public static final Status FAILED_PRECONDITION = CanonicalCode.FAILED_PRECONDITION.toStatus(); + + /** + * The operation was aborted, typically due to a concurrency issue like sequencer check failures, + * transaction aborts, etc. See {@link CanonicalCode#ABORTED}. + * + * @since 0.5 + */ + public static final Status ABORTED = CanonicalCode.ABORTED.toStatus(); + + /** + * Operation was attempted past the valid range. See {@link CanonicalCode#OUT_OF_RANGE}. + * + * @since 0.5 + */ + public static final Status OUT_OF_RANGE = CanonicalCode.OUT_OF_RANGE.toStatus(); + + /** + * Operation is not implemented or not supported/enabled in this service. + * + * @since 0.5 + */ + public static final Status UNIMPLEMENTED = CanonicalCode.UNIMPLEMENTED.toStatus(); + + /** + * Internal errors. See {@link CanonicalCode#INTERNAL}. + * + * @since 0.5 + */ + public static final Status INTERNAL = CanonicalCode.INTERNAL.toStatus(); + + /** + * The service is currently unavailable. See {@link CanonicalCode#UNAVAILABLE}. + * + * @since 0.5 + */ + public static final Status UNAVAILABLE = CanonicalCode.UNAVAILABLE.toStatus(); + + /** + * Unrecoverable data loss or corruption. + * + * @since 0.5 + */ + public static final Status DATA_LOSS = CanonicalCode.DATA_LOSS.toStatus(); + + // The canonical code of this message. + private final CanonicalCode canonicalCode; + + // An additional error message. + @Nullable private final String description; + + private Status(CanonicalCode canonicalCode, @Nullable String description) { + this.canonicalCode = Utils.checkNotNull(canonicalCode, "canonicalCode"); + this.description = description; + } + + /** + * Creates a derived instance of {@code Status} with the given description. + * + * @param description the new description of the {@code Status}. + * @return The newly created {@code Status} with the given description. + * @since 0.5 + */ + public Status withDescription(String description) { + if (Utils.equalsObjects(this.description, description)) { + return this; + } + return new Status(this.canonicalCode, description); + } + + /** + * Returns the canonical status code. + * + * @return the canonical status code. + * @since 0.5 + */ + public CanonicalCode getCanonicalCode() { + return canonicalCode; + } + + /** + * Returns the description of this {@code Status} for human consumption. + * + * @return the description of this {@code Status}. + * @since 0.5 + */ + @Nullable + /*@Deterministic*/ + public String getDescription() { + return description; + } + + /** + * Returns {@code true} if this {@code Status} is OK, i.e., not an error. + * + * @return {@code true} if this {@code Status} is OK. + * @since 0.5 + */ + public boolean isOk() { + return CanonicalCode.OK == canonicalCode; + } + + /** + * Equality on Statuses is not well defined. Instead, do comparison based on their CanonicalCode + * with {@link #getCanonicalCode}. The description of the Status is unlikely to be stable, and + * additional fields may be added to Status in the future. + */ + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Status)) { + return false; + } + + Status that = (Status) obj; + return canonicalCode == that.canonicalCode + && Utils.equalsObjects(description, that.description); + } + + /** + * Hash codes on Statuses are not well defined. + * + * @see #equals + */ + @Override + public int hashCode() { + return Arrays.hashCode(new Object[] {canonicalCode, description}); + } + + @Override + public String toString() { + return "Status{canonicalCode=" + canonicalCode + ", description=" + description + "}"; + } +} diff --git a/api/src/main/java/io/opencensus/trace/TraceComponent.java b/api/src/main/java/io/opencensus/trace/TraceComponent.java new file mode 100644 index 00000000..d98d0f9e --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceComponent.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.common.Clock; +import io.opencensus.internal.ZeroTimeClock; +import io.opencensus.trace.config.TraceConfig; +import io.opencensus.trace.export.ExportComponent; +import io.opencensus.trace.propagation.PropagationComponent; + +/** + * Class that holds the implementation instances for {@link Tracer}, {@link PropagationComponent}, + * {@link Clock}, {@link ExportComponent} and {@link TraceConfig}. + * + * <p>Unless otherwise noted all methods (on component) results are cacheable. + * + * @since 0.5 + */ +public abstract class TraceComponent { + + /** + * Returns the {@link Tracer} with the provided implementations. If no implementation is provided + * then no-op implementations will be used. + * + * @return the {@code Tracer} implementation. + * @since 0.5 + */ + public abstract Tracer getTracer(); + + /** + * Returns the {@link PropagationComponent} with the provided implementation. If no implementation + * is provided then no-op implementation will be used. + * + * @return the {@code PropagationComponent} implementation. + * @since 0.5 + */ + public abstract PropagationComponent getPropagationComponent(); + + /** + * Returns the {@link Clock} with the provided implementation. + * + * @return the {@code Clock} implementation. + * @since 0.5 + */ + public abstract Clock getClock(); + + /** + * Returns the {@link ExportComponent} with the provided implementation. If no implementation is + * provided then no-op implementations will be used. + * + * @return the {@link ExportComponent} implementation. + * @since 0.5 + */ + public abstract ExportComponent getExportComponent(); + + /** + * Returns the {@link TraceConfig} with the provided implementation. If no implementation is + * provided then no-op implementations will be used. + * + * @return the {@link TraceConfig} implementation. + * @since 0.5 + */ + public abstract TraceConfig getTraceConfig(); + + /** + * Returns an instance that contains no-op implementations for all the instances. + * + * @return an instance that contains no-op implementations for all the instances. + */ + static TraceComponent newNoopTraceComponent() { + return new NoopTraceComponent(); + } + + private static final class NoopTraceComponent extends TraceComponent { + private final ExportComponent noopExportComponent = ExportComponent.newNoopExportComponent(); + + @Override + public Tracer getTracer() { + return Tracer.getNoopTracer(); + } + + @Override + public PropagationComponent getPropagationComponent() { + return PropagationComponent.getNoopPropagationComponent(); + } + + @Override + public Clock getClock() { + return ZeroTimeClock.getInstance(); + } + + @Override + public ExportComponent getExportComponent() { + return noopExportComponent; + } + + @Override + public TraceConfig getTraceConfig() { + return TraceConfig.getNoopTraceConfig(); + } + + private NoopTraceComponent() {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/TraceId.java b/api/src/main/java/io/opencensus/trace/TraceId.java new file mode 100644 index 00000000..465e4d4a --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceId.java @@ -0,0 +1,236 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.common.Internal; +import io.opencensus.internal.Utils; +import java.util.Arrays; +import java.util.Random; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents a trace identifier. A valid trace identifier is a 16-byte array with at + * least one non-zero byte. + * + * @since 0.5 + */ +@Immutable +public final class TraceId implements Comparable<TraceId> { + /** + * The size in bytes of the {@code TraceId}. + * + * @since 0.5 + */ + public static final int SIZE = 16; + + private static final int HEX_SIZE = 32; + + /** + * The invalid {@code TraceId}. All bytes are '\0'. + * + * @since 0.5 + */ + public static final TraceId INVALID = new TraceId(new byte[SIZE]); + + // The internal representation of the TraceId. + private final byte[] bytes; + + private TraceId(byte[] bytes) { + this.bytes = bytes; + } + + /** + * Returns a {@code TraceId} built from a byte representation. + * + * <p>Equivalent with: + * + * <pre>{@code + * TraceId.fromBytes(buffer, 0); + * }</pre> + * + * @param buffer the representation of the {@code TraceId}. + * @return a {@code TraceId} whose representation is given by the {@code buffer} parameter. + * @throws NullPointerException if {@code buffer} is null. + * @throws IllegalArgumentException if {@code buffer.length} is not {@link TraceId#SIZE}. + * @since 0.5 + */ + public static TraceId fromBytes(byte[] buffer) { + Utils.checkNotNull(buffer, "buffer"); + Utils.checkArgument( + buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length); + byte[] bytesCopied = Arrays.copyOf(buffer, SIZE); + return new TraceId(bytesCopied); + } + + /** + * Returns a {@code TraceId} whose representation is copied from the {@code src} beginning at the + * {@code srcOffset} offset. + * + * @param src the buffer where the representation of the {@code TraceId} is copied. + * @param srcOffset the offset in the buffer where the representation of the {@code TraceId} + * begins. + * @return a {@code TraceId} whose representation is copied from the buffer. + * @throws NullPointerException if {@code src} is null. + * @throws IndexOutOfBoundsException if {@code srcOffset+TraceId.SIZE} is greater than {@code + * src.length}. + * @since 0.5 + */ + public static TraceId fromBytes(byte[] src, int srcOffset) { + byte[] bytes = new byte[SIZE]; + System.arraycopy(src, srcOffset, bytes, 0, SIZE); + return new TraceId(bytes); + } + + /** + * Returns a {@code TraceId} built from a lowercase base16 representation. + * + * @param src the lowercase base16 representation. + * @return a {@code TraceId} built from a lowercase base16 representation. + * @throws NullPointerException if {@code src} is null. + * @throws IllegalArgumentException if {@code src.length} is not {@code 2 * TraceId.SIZE} OR if + * the {@code str} has invalid characters. + * @since 0.11 + */ + public static TraceId fromLowerBase16(CharSequence src) { + Utils.checkArgument( + src.length() == HEX_SIZE, "Invalid size: expected %s, got %s", HEX_SIZE, src.length()); + return new TraceId(LowerCaseBase16Encoding.decodeToBytes(src)); + } + + /** + * Generates a new random {@code TraceId}. + * + * @param random the random number generator. + * @return a new valid {@code TraceId}. + * @since 0.5 + */ + public static TraceId generateRandomId(Random random) { + byte[] bytes = new byte[SIZE]; + do { + random.nextBytes(bytes); + } while (Arrays.equals(bytes, INVALID.bytes)); + return new TraceId(bytes); + } + + /** + * Returns the 16-bytes array representation of the {@code TraceId}. + * + * @return the 16-bytes array representation of the {@code TraceId}. + * @since 0.5 + */ + public byte[] getBytes() { + return Arrays.copyOf(bytes, SIZE); + } + + /** + * Copies the byte array representations of the {@code TraceId} into the {@code dest} beginning at + * the {@code destOffset} offset. + * + * <p>Equivalent with (but faster because it avoids any new allocations): + * + * <pre>{@code + * System.arraycopy(getBytes(), 0, dest, destOffset, TraceId.SIZE); + * }</pre> + * + * @param dest the destination buffer. + * @param destOffset the starting offset in the destination buffer. + * @throws NullPointerException if {@code dest} is null. + * @throws IndexOutOfBoundsException if {@code destOffset+TraceId.SIZE} is greater than {@code + * dest.length}. + * @since 0.5 + */ + public void copyBytesTo(byte[] dest, int destOffset) { + System.arraycopy(bytes, 0, dest, destOffset, SIZE); + } + + /** + * Returns whether the {@code TraceId} is valid. A valid trace identifier is a 16-byte array with + * at least one non-zero byte. + * + * @return {@code true} if the {@code TraceId} is valid. + * @since 0.5 + */ + public boolean isValid() { + return !Arrays.equals(bytes, INVALID.bytes); + } + + /** + * Returns the lowercase base16 encoding of this {@code TraceId}. + * + * @return the lowercase base16 encoding of this {@code TraceId}. + * @since 0.11 + */ + public String toLowerBase16() { + return LowerCaseBase16Encoding.encodeToString(bytes); + } + + /** + * Returns the lower 8 bytes of the trace-id as a long value, assuming little-endian order. This + * is used in ProbabilitySampler. + * + * <p>This method is marked as internal and subject to change. + * + * @return the lower 8 bytes of the trace-id as a long value, assuming little-endian order. + */ + @Internal + public long getLowerLong() { + long result = 0; + for (int i = 0; i < Long.SIZE / Byte.SIZE; i++) { + result <<= Byte.SIZE; + result |= (bytes[i] & 0xff); + } + if (result < 0) { + return -result; + } + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof TraceId)) { + return false; + } + + TraceId that = (TraceId) obj; + return Arrays.equals(bytes, that.bytes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(bytes); + } + + @Override + public String toString() { + return "TraceId{traceId=" + toLowerBase16() + "}"; + } + + @Override + public int compareTo(TraceId that) { + for (int i = 0; i < SIZE; i++) { + if (bytes[i] != that.bytes[i]) { + return bytes[i] < that.bytes[i] ? -1 : 1; + } + } + return 0; + } +} diff --git a/api/src/main/java/io/opencensus/trace/TraceOptions.java b/api/src/main/java/io/opencensus/trace/TraceOptions.java new file mode 100644 index 00000000..218f4dab --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/TraceOptions.java @@ -0,0 +1,280 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.internal.DefaultVisibilityForTesting; +import io.opencensus.internal.Utils; +import java.util.Arrays; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * A class that represents global trace options. These options are propagated to all child {@link + * io.opencensus.trace.Span spans}. These determine features such as whether a {@code Span} should + * be traced. It is implemented as a bitmask. + * + * @since 0.5 + */ +@Immutable +public final class TraceOptions { + // Default options. Nothing set. + private static final byte DEFAULT_OPTIONS = 0; + // Bit to represent whether trace is sampled or not. + private static final byte IS_SAMPLED = 0x1; + + /** + * The size in bytes of the {@code TraceOptions}. + * + * @since 0.5 + */ + public static final int SIZE = 1; + + /** + * The default {@code TraceOptions}. + * + * @since 0.5 + */ + public static final TraceOptions DEFAULT = fromByte(DEFAULT_OPTIONS); + + // The set of enabled features is determined by all the enabled bits. + private final byte options; + + // Creates a new {@code TraceOptions} with the given options. + private TraceOptions(byte options) { + this.options = options; + } + + /** + * Returns a {@code TraceOptions} built from a byte representation. + * + * <p>Equivalent with: + * + * <pre>{@code + * TraceOptions.fromBytes(buffer, 0); + * }</pre> + * + * @param buffer the representation of the {@code TraceOptions}. + * @return a {@code TraceOptions} whose representation is given by the {@code buffer} parameter. + * @throws NullPointerException if {@code buffer} is null. + * @throws IllegalArgumentException if {@code buffer.length} is not {@link TraceOptions#SIZE}. + * @since 0.5 + * @deprecated use {@link #fromByte(byte)}. + */ + @Deprecated + public static TraceOptions fromBytes(byte[] buffer) { + Utils.checkNotNull(buffer, "buffer"); + Utils.checkArgument( + buffer.length == SIZE, "Invalid size: expected %s, got %s", SIZE, buffer.length); + return fromByte(buffer[0]); + } + + /** + * Returns a {@code TraceOptions} whose representation is copied from the {@code src} beginning at + * the {@code srcOffset} offset. + * + * @param src the buffer where the representation of the {@code TraceOptions} is copied. + * @param srcOffset the offset in the buffer where the representation of the {@code TraceOptions} + * begins. + * @return a {@code TraceOptions} whose representation is copied from the buffer. + * @throws NullPointerException if {@code src} is null. + * @throws IndexOutOfBoundsException if {@code srcOffset+TraceOptions.SIZE} is greater than {@code + * src.length}. + * @since 0.5 + * @deprecated use {@link #fromByte(byte)}. + */ + @Deprecated + public static TraceOptions fromBytes(byte[] src, int srcOffset) { + Utils.checkIndex(srcOffset, src.length); + return fromByte(src[srcOffset]); + } + + /** + * Returns a {@code TraceOptions} whose representation is {@code src}. + * + * @param src the byte representation of the {@code TraceOptions}. + * @return a {@code TraceOptions} whose representation is {@code src}. + * @since 0.16 + */ + public static TraceOptions fromByte(byte src) { + // TODO(bdrutu): OPTIMIZATION: Cache all the 256 possible objects and return from the cache. + return new TraceOptions(src); + } + + /** + * Returns the one byte representation of the {@code TraceOptions}. + * + * @return the one byte representation of the {@code TraceOptions}. + * @since 0.16 + */ + public byte getByte() { + return options; + } + + /** + * Returns the 1-byte array representation of the {@code TraceOptions}. + * + * @return the 1-byte array representation of the {@code TraceOptions}. + * @since 0.5 + * @deprecated use {@link #getByte()}. + */ + @Deprecated + public byte[] getBytes() { + byte[] bytes = new byte[SIZE]; + bytes[0] = options; + return bytes; + } + + /** + * Copies the byte representations of the {@code TraceOptions} into the {@code dest} beginning at + * the {@code destOffset} offset. + * + * <p>Equivalent with (but faster because it avoids any new allocations): + * + * <pre>{@code + * System.arraycopy(getBytes(), 0, dest, destOffset, TraceOptions.SIZE); + * }</pre> + * + * @param dest the destination buffer. + * @param destOffset the starting offset in the destination buffer. + * @throws NullPointerException if {@code dest} is null. + * @throws IndexOutOfBoundsException if {@code destOffset+TraceOptions.SIZE} is greater than + * {@code dest.length}. + * @since 0.5 + */ + public void copyBytesTo(byte[] dest, int destOffset) { + Utils.checkIndex(destOffset, dest.length); + dest[destOffset] = options; + } + + /** + * Returns a new {@link Builder} with default options. + * + * @return a new {@code Builder} with default options. + * @since 0.5 + */ + public static Builder builder() { + return new Builder(DEFAULT_OPTIONS); + } + + /** + * Returns a new {@link Builder} with all given options set. + * + * @param traceOptions the given options set. + * @return a new {@code Builder} with all given options set. + * @since 0.5 + */ + public static Builder builder(TraceOptions traceOptions) { + return new Builder(traceOptions.options); + } + + /** + * Returns a boolean indicating whether this {@code Span} is part of a sampled trace and data + * should be exported to a persistent store. + * + * @return a boolean indicating whether the trace is sampled. + * @since 0.5 + */ + public boolean isSampled() { + return hasOption(IS_SAMPLED); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof TraceOptions)) { + return false; + } + + TraceOptions that = (TraceOptions) obj; + return options == that.options; + } + + @Override + public int hashCode() { + return Arrays.hashCode(new byte[] {options}); + } + + @Override + public String toString() { + return "TraceOptions{sampled=" + isSampled() + "}"; + } + + /** + * Builder class for {@link TraceOptions}. + * + * @since 0.5 + */ + public static final class Builder { + private byte options; + + private Builder(byte options) { + this.options = options; + } + + /** + * Sets the sampling bit in the options to true. + * + * @deprecated Use {@code Builder.setIsSampled(true)}. + * @return this. + * @since 0.5 + */ + @Deprecated + public Builder setIsSampled() { + return setIsSampled(true); + } + + /** + * Sets the sampling bit in the options. + * + * @param isSampled the sampling bit. + * @return this. + * @since 0.7 + */ + public Builder setIsSampled(boolean isSampled) { + if (isSampled) { + options = (byte) (options | IS_SAMPLED); + } else { + options = (byte) (options & ~IS_SAMPLED); + ; + } + return this; + } + + /** + * Builds and returns a {@code TraceOptions} with the desired options. + * + * @return a {@code TraceOptions} with the desired options. + * @since 0.5 + */ + public TraceOptions build() { + return fromByte(options); + } + } + + // Returns the current set of options bitmask. + @DefaultVisibilityForTesting + byte getOptions() { + return options; + } + + private boolean hasOption(int mask) { + return (this.options & mask) != 0; + } +} diff --git a/api/src/main/java/io/opencensus/trace/Tracer.java b/api/src/main/java/io/opencensus/trace/Tracer.java new file mode 100644 index 00000000..a2c0a239 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Tracer.java @@ -0,0 +1,370 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.errorprone.annotations.MustBeClosed; +import io.opencensus.common.Scope; +import io.opencensus.internal.Utils; +import io.opencensus.trace.SpanBuilder.NoopSpanBuilder; +import java.util.concurrent.Callable; +import javax.annotation.Nullable; + +/** + * Tracer is a simple, thin class for {@link Span} creation and in-process context interaction. + * + * <p>Users may choose to use manual or automatic Context propagation. Because of that this class + * offers APIs to facilitate both usages. + * + * <p>The automatic context propagation is done using {@link io.grpc.Context} which is a gRPC + * independent implementation for in-process Context propagation mechanism which can carry + * scoped-values across API boundaries and between threads. Users of the library must propagate the + * {@link io.grpc.Context} between different threads. + * + * <p>Example usage with automatic context propagation: + * + * <pre>{@code + * class MyClass { + * private static final Tracer tracer = Tracing.getTracer(); + * void doWork() { + * try(Scope ss = tracer.spanBuilder("MyClass.DoWork").startScopedSpan()) { + * tracer.getCurrentSpan().addAnnotation("Starting the work."); + * doWorkInternal(); + * tracer.getCurrentSpan().addAnnotation("Finished working."); + * } + * } + * } + * }</pre> + * + * <p>Example usage with manual context propagation: + * + * <pre>{@code + * class MyClass { + * private static final Tracer tracer = Tracing.getTracer(); + * void doWork(Span parent) { + * Span childSpan = tracer.spanBuilderWithExplicitParent("MyChildSpan", parent).startSpan(); + * childSpan.addAnnotation("Starting the work."); + * try { + * doSomeWork(childSpan); // Manually propagate the new span down the stack. + * } finally { + * // To make sure we end the span even in case of an exception. + * childSpan.end(); // Manually end the span. + * } + * } + * } + * }</pre> + * + * @since 0.5 + */ +public abstract class Tracer { + private static final NoopTracer noopTracer = new NoopTracer(); + + /** + * Returns the no-op implementation of the {@code Tracer}. + * + * @return the no-op implementation of the {@code Tracer}. + */ + static Tracer getNoopTracer() { + return noopTracer; + } + + /** + * Gets the current Span from the current Context. + * + * <p>To install a {@link Span} to the current Context use {@link #withSpan(Span)} OR use {@link + * SpanBuilder#startScopedSpan} methods to start a new {@code Span}. + * + * <p>startSpan methods do NOT modify the current Context {@code Span}. + * + * @return a default {@code Span} that does nothing and has an invalid {@link SpanContext} if no + * {@code Span} is associated with the current Context, otherwise the current {@code Span} + * from the Context. + * @since 0.5 + */ + public final Span getCurrentSpan() { + Span currentSpan = CurrentSpanUtils.getCurrentSpan(); + return currentSpan != null ? currentSpan : BlankSpan.INSTANCE; + } + + /** + * Enters the scope of code where the given {@link Span} is in the current Context, and returns an + * object that represents that scope. The scope is exited when the returned object is closed. + * + * <p>Supports try-with-resource idiom. + * + * <p>Can be called with {@link BlankSpan} to enter a scope of code where tracing is stopped. + * + * <p>Example of usage: + * + * <pre>{@code + * private static Tracer tracer = Tracing.getTracer(); + * void doWork() { + * // Create a Span as a child of the current Span. + * Span span = tracer.spanBuilder("my span").startSpan(); + * try (Scope ws = tracer.withSpan(span)) { + * tracer.getCurrentSpan().addAnnotation("my annotation"); + * doSomeOtherWork(); // Here "span" is the current Span. + * } + * span.end(); + * } + * }</pre> + * + * <p>Prior to Java SE 7, you can use a finally block to ensure that a resource is closed + * regardless of whether the try statement completes normally or abruptly. + * + * <p>Example of usage prior to Java SE7: + * + * <pre>{@code + * private static Tracer tracer = Tracing.getTracer(); + * void doWork() { + * // Create a Span as a child of the current Span. + * Span span = tracer.spanBuilder("my span").startSpan(); + * Scope ws = tracer.withSpan(span); + * try { + * tracer.getCurrentSpan().addAnnotation("my annotation"); + * doSomeOtherWork(); // Here "span" is the current Span. + * } finally { + * ws.close(); + * } + * span.end(); + * } + * }</pre> + * + * @param span The {@link Span} to be set to the current Context. + * @return an object that defines a scope where the given {@link Span} will be set to the current + * Context. + * @throws NullPointerException if {@code span} is {@code null}. + * @since 0.5 + */ + @MustBeClosed + public final Scope withSpan(Span span) { + return CurrentSpanUtils.withSpan(Utils.checkNotNull(span, "span"), /* endSpan= */ false); + } + + /** + * Returns a {@link Runnable} that runs the given task with the given {@code Span} in the current + * context. + * + * <p>Users may consider to use {@link SpanBuilder#startSpanAndRun(Runnable)}. + * + * <p>Any error will end up as a {@link Status#UNKNOWN}. + * + * <p>IMPORTANT: Caller must manually propagate the entire {@code io.grpc.Context} when wraps a + * {@code Runnable}, see the examples. + * + * <p>IMPORTANT: Caller must manually end the {@code Span} within the {@code Runnable}, or after + * the {@code Runnable} is executed. + * + * <p>Example with Executor wrapped with {@link io.grpc.Context#currentContextExecutor}: + * + * <pre><code> + * class MyClass { + * private static Tracer tracer = Tracing.getTracer(); + * void handleRequest(Executor executor) { + * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan(); + * executor.execute(tracer.withSpan(span, new Runnable() { + * {@literal @}Override + * public void run() { + * try { + * sendResult(); + * } finally { + * span.end(); + * } + * } + * })); + * } + * } + * </code></pre> + * + * <p>Example without Executor wrapped with {@link io.grpc.Context#currentContextExecutor}: + * + * <pre><code> + * class MyClass { + * private static Tracer tracer = Tracing.getTracer(); + * void handleRequest(Executor executor) { + * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan(); + * executor.execute(Context.wrap(tracer.withSpan(span, new Runnable() { + * {@literal @}Override + * public void run() { + * try { + * sendResult(); + * } finally { + * span.end(); + * } + * } + * }))); + * } + * } + * </code></pre> + * + * @param span the {@code Span} to be set as current. + * @param runnable the {@code Runnable} to withSpan in the {@code Span}. + * @return the {@code Runnable}. + * @since 0.11.0 + */ + public final Runnable withSpan(Span span, Runnable runnable) { + return CurrentSpanUtils.withSpan(span, /* endSpan= */ false, runnable); + } + + /** + * Returns a {@link Callable} that runs the given task with the given {@code Span} in the current + * context. + * + * <p>Users may consider to use {@link SpanBuilder#startSpanAndCall(Callable)}. + * + * <p>Any error will end up as a {@link Status#UNKNOWN}. + * + * <p>IMPORTANT: Caller must manually propagate the entire {@code io.grpc.Context} when wraps a + * {@code Callable}, see the examples. + * + * <p>IMPORTANT: Caller must manually end the {@code Span} within the {@code Callable}, or after + * the {@code Callable} is executed. + * + * <p>Example with Executor wrapped with {@link io.grpc.Context#currentContextExecutor}: + * + * <pre><code> + * class MyClass { + * private static Tracer tracer = Tracing.getTracer(); + * void handleRequest(Executor executor) { + * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan(); + * executor.execute(tracer.withSpan(span, {@code new Callable<MyResult>()} { + * {@literal @}Override + * public MyResult call() throws Exception { + * try { + * return sendResult(); + * } finally { + * span.end(); + * } + * } + * })); + * } + * } + * </code></pre> + * + * <p>Example without Executor wrapped with {@link io.grpc.Context#currentContextExecutor}: + * + * <pre><code> + * class MyClass { + * private static Tracer tracer = Tracing.getTracer(); + * void handleRequest(Executor executor) { + * Span span = tracer.spanBuilder("MyRunnableSpan").startSpan(); + * executor.execute(Context.wrap(tracer.withSpan(span, {@code new Callable<MyResult>()} { + * {@literal @}Override + * public MyResult call() throws Exception { + * try { + * return sendResult(); + * } finally { + * span.end(); + * } + * } + * }))); + * } + * } + * </code></pre> + * + * @param span the {@code Span} to be set as current. + * @param callable the {@code Callable} to run in the {@code Span}. + * @return the {@code Callable}. + * @since 0.11.0 + */ + public final <C> Callable<C> withSpan(Span span, final Callable<C> callable) { + return CurrentSpanUtils.withSpan(span, /* endSpan= */ false, callable); + } + + /** + * Returns a {@link SpanBuilder} to create and start a new child {@link Span} as a child of to the + * current {@code Span} if any, otherwise creates a root {@code Span}. + * + * <p>See {@link SpanBuilder} for usage examples. + * + * <p>This <b>must</b> be used to create a {@code Span} when automatic Context propagation is + * used. + * + * <p>This is equivalent with: + * + * <pre>{@code + * tracer.spanBuilderWithExplicitParent("MySpanName",tracer.getCurrentSpan()); + * }</pre> + * + * @param spanName The name of the returned Span. + * @return a {@code SpanBuilder} to create and start a new {@code Span}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @since 0.5 + */ + public final SpanBuilder spanBuilder(String spanName) { + return spanBuilderWithExplicitParent(spanName, CurrentSpanUtils.getCurrentSpan()); + } + + /** + * Returns a {@link SpanBuilder} to create and start a new child {@link Span} (or root if parent + * is {@code null} or has an invalid {@link SpanContext}), with parent being the designated {@code + * Span}. + * + * <p>See {@link SpanBuilder} for usage examples. + * + * <p>This <b>must</b> be used to create a {@code Span} when manual Context propagation is used OR + * when creating a root {@code Span} with a {@code null} parent. + * + * @param spanName The name of the returned Span. + * @param parent The parent of the returned Span. If {@code null} the {@code SpanBuilder} will + * build a root {@code Span}. + * @return a {@code SpanBuilder} to create and start a new {@code Span}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @since 0.5 + */ + public abstract SpanBuilder spanBuilderWithExplicitParent(String spanName, @Nullable Span parent); + + /** + * Returns a {@link SpanBuilder} to create and start a new child {@link Span} (or root if parent + * is {@link SpanContext#INVALID} or {@code null}), with parent being the remote {@link Span} + * designated by the {@link SpanContext}. + * + * <p>See {@link SpanBuilder} for usage examples. + * + * <p>This <b>must</b> be used to create a {@code Span} when the parent is in a different process. + * This is only intended for use by RPC systems or similar. + * + * <p>If no {@link SpanContext} OR fail to parse the {@link SpanContext} on the server side, users + * must call this method with a {@code null} remote parent {@code SpanContext}. + * + * @param spanName The name of the returned Span. + * @param remoteParentSpanContext The remote parent of the returned Span. + * @return a {@code SpanBuilder} to create and start a new {@code Span}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @since 0.5 + */ + public abstract SpanBuilder spanBuilderWithRemoteParent( + String spanName, @Nullable SpanContext remoteParentSpanContext); + + // No-Op implementation of the Tracer. + private static final class NoopTracer extends Tracer { + + @Override + public SpanBuilder spanBuilderWithExplicitParent(String spanName, @Nullable Span parent) { + return NoopSpanBuilder.createWithParent(spanName, parent); + } + + @Override + public SpanBuilder spanBuilderWithRemoteParent( + String spanName, @Nullable SpanContext remoteParentSpanContext) { + return NoopSpanBuilder.createWithRemoteParent(spanName, remoteParentSpanContext); + } + + private NoopTracer() {} + } + + protected Tracer() {} +} diff --git a/api/src/main/java/io/opencensus/trace/Tracestate.java b/api/src/main/java/io/opencensus/trace/Tracestate.java new file mode 100644 index 00000000..dae587c8 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Tracestate.java @@ -0,0 +1,273 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.ExperimentalApi; +import io.opencensus.internal.Utils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.concurrent.Immutable; + +/** + * Carries tracing-system specific context in a list of key-value pairs. TraceState allows different + * vendors propagate additional information and inter-operate with their legacy Id formats. + * + * <p>Implementation is optimized for a small list of key-value pairs. + * + * <p>Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter, + * and can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and + * forward slashes /. + * + * <p>Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the + * range 0x20 to 0x7E) except comma , and =. + * + * @since 0.16 + */ +@Immutable +@AutoValue +@ExperimentalApi +public abstract class Tracestate { + private static final int KEY_MAX_SIZE = 256; + private static final int VALUE_MAX_SIZE = 256; + private static final int MAX_KEY_VALUE_PAIRS = 32; + + /** + * Returns the value to which the specified key is mapped, or null if this map contains no mapping + * for the key. + * + * @param key with which the specified value is to be associated + * @return the value to which the specified key is mapped, or null if this map contains no mapping + * for the key. + * @since 0.16 + */ + @javax.annotation.Nullable + public String get(String key) { + for (Entry entry : getEntries()) { + if (entry.getKey().equals(key)) { + return entry.getValue(); + } + } + return null; + } + + /** + * Returns a {@link List} view of the mappings contained in this {@code TraceState}. + * + * @return a {@link List} view of the mappings contained in this {@code TraceState}. + * @since 0.16 + */ + public abstract List<Entry> getEntries(); + + /** + * Returns a {@code Builder} based on an empty {@code Tracestate}. + * + * @return a {@code Builder} based on an empty {@code Tracestate}. + * @since 0.16 + */ + public static Builder builder() { + return new Builder(Builder.EMPTY); + } + + /** + * Returns a {@code Builder} based on this {@code Tracestate}. + * + * @return a {@code Builder} based on this {@code Tracestate}. + * @since 0.16 + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Builder class for {@link MessageEvent}. + * + * @since 0.16 + */ + @ExperimentalApi + public static final class Builder { + private final Tracestate parent; + @javax.annotation.Nullable private ArrayList<Entry> entries; + + // Needs to be in this class to avoid initialization deadlock because super class depends on + // subclass (the auto-value generate class). + private static final Tracestate EMPTY = create(Collections.<Entry>emptyList()); + + private Builder(Tracestate parent) { + Utils.checkNotNull(parent, "parent"); + this.parent = parent; + this.entries = null; + } + + /** + * Adds or updates the {@code Entry} that has the given {@code key} if it is present. The new + * {@code Entry} will always be added in the front of the list of entries. + * + * @param key the key for the {@code Entry} to be added. + * @param value the value for the {@code Entry} to be added. + * @return this. + * @since 0.16 + */ + @SuppressWarnings("nullness") + public Builder set(String key, String value) { + // Initially create the Entry to validate input. + Entry entry = Entry.create(key, value); + if (entries == null) { + // Copy entries from the parent. + entries = new ArrayList<Entry>(parent.getEntries()); + } + for (int i = 0; i < entries.size(); i++) { + if (entries.get(i).getKey().equals(entry.getKey())) { + entries.remove(i); + // Exit now because the entries list cannot contain duplicates. + break; + } + } + // Inserts the element at the front of this list. + entries.add(0, entry); + return this; + } + + /** + * Removes the {@code Entry} that has the given {@code key} if it is present. + * + * @param key the key for the {@code Entry} to be removed. + * @return this. + * @since 0.16 + */ + @SuppressWarnings("nullness") + public Builder remove(String key) { + Utils.checkNotNull(key, "key"); + if (entries == null) { + // Copy entries from the parent. + entries = new ArrayList<Entry>(parent.getEntries()); + } + for (int i = 0; i < entries.size(); i++) { + if (entries.get(i).getKey().equals(key)) { + entries.remove(i); + // Exit now because the entries list cannot contain duplicates. + break; + } + } + return this; + } + + /** + * Builds a TraceState by adding the entries to the parent in front of the key-value pairs list + * and removing duplicate entries. + * + * @return a TraceState with the new entries. + * @since 0.16 + */ + public Tracestate build() { + if (entries == null) { + return parent; + } + return Tracestate.create(entries); + } + } + + /** + * Immutable key-value pair for {@code Tracestate}. + * + * @since 0.16 + */ + @Immutable + @AutoValue + @ExperimentalApi + public abstract static class Entry { + /** + * Creates a new {@code Entry} for the {@code Tracestate}. + * + * @param key the Entry's key. + * @param value the Entry's value. + * @since 0.16 + */ + public static Entry create(String key, String value) { + Utils.checkNotNull(key, "key"); + Utils.checkNotNull(value, "value"); + Utils.checkArgument(validateKey(key), "Invalid key %s", key); + Utils.checkArgument(validateValue(value), "Invalid value %s", value); + return new AutoValue_Tracestate_Entry(key, value); + } + + /** + * Returns the key {@code String}. + * + * @return the key {@code String}. + * @since 0.16 + */ + public abstract String getKey(); + + /** + * Returns the value {@code String}. + * + * @return the value {@code String}. + * @since 0.16 + */ + public abstract String getValue(); + + Entry() {} + } + + // Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter, and + // can only contain lowercase letters a-z, digits 0-9, underscores _, dashes -, asterisks *, and + // forward slashes /. + private static boolean validateKey(String key) { + if (key.length() > KEY_MAX_SIZE + || key.isEmpty() + || key.charAt(0) < 'a' + || key.charAt(0) > 'z') { + return false; + } + for (int i = 1; i < key.length(); i++) { + char c = key.charAt(i); + if (!(c >= 'a' && c <= 'z') + && !(c >= '0' && c <= '9') + && c != '_' + && c != '-' + && c != '*' + && c != '/') { + return false; + } + } + return true; + } + + // Value is opaque string up to 256 characters printable ASCII RFC0020 characters (i.e., the range + // 0x20 to 0x7E) except comma , and =. + private static boolean validateValue(String value) { + if (value.length() > VALUE_MAX_SIZE || value.charAt(value.length() - 1) == ' ' /* '\u0020' */) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == ',' || c == '=' || c < ' ' /* '\u0020' */ || c > '~' /* '\u007E' */) { + return false; + } + } + return true; + } + + private static Tracestate create(List<Entry> entries) { + Utils.checkState(entries.size() <= MAX_KEY_VALUE_PAIRS, "Invalid size"); + return new AutoValue_Tracestate(Collections.unmodifiableList(entries)); + } + + Tracestate() {} +} diff --git a/api/src/main/java/io/opencensus/trace/Tracing.java b/api/src/main/java/io/opencensus/trace/Tracing.java new file mode 100644 index 00000000..f55cd775 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/Tracing.java @@ -0,0 +1,125 @@ +/* + * Copyright 2016-17, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace; + +import io.opencensus.common.Clock; +import io.opencensus.internal.DefaultVisibilityForTesting; +import io.opencensus.internal.Provider; +import io.opencensus.trace.config.TraceConfig; +import io.opencensus.trace.export.ExportComponent; +import io.opencensus.trace.propagation.PropagationComponent; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Class that manages a global instance of the {@link TraceComponent}. + * + * @since 0.5 + */ +public final class Tracing { + private static final Logger logger = Logger.getLogger(Tracing.class.getName()); + private static final TraceComponent traceComponent = + loadTraceComponent(TraceComponent.class.getClassLoader()); + + /** + * Returns the global {@link Tracer}. + * + * @return the global {@code Tracer}. + * @since 0.5 + */ + public static Tracer getTracer() { + return traceComponent.getTracer(); + } + + /** + * Returns the global {@link PropagationComponent}. + * + * @return the global {@code PropagationComponent}. + * @since 0.5 + */ + public static PropagationComponent getPropagationComponent() { + return traceComponent.getPropagationComponent(); + } + + /** + * Returns the global {@link Clock}. + * + * @return the global {@code Clock}. + * @since 0.5 + */ + public static Clock getClock() { + return traceComponent.getClock(); + } + + /** + * Returns the global {@link ExportComponent}. + * + * @return the global {@code ExportComponent}. + * @since 0.5 + */ + public static ExportComponent getExportComponent() { + return traceComponent.getExportComponent(); + } + + /** + * Returns the global {@link TraceConfig}. + * + * @return the global {@code TraceConfig}. + * @since 0.5 + */ + public static TraceConfig getTraceConfig() { + return traceComponent.getTraceConfig(); + } + + // Any provider that may be used for TraceComponent can be added here. + @DefaultVisibilityForTesting + static TraceComponent loadTraceComponent(@Nullable ClassLoader classLoader) { + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impl.trace.TraceComponentImpl", /*initialize=*/ true, classLoader), + TraceComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load full implementation for TraceComponent, now trying to load lite " + + "implementation.", + e); + } + try { + // Call Class.forName with literal string name of the class to help shading tools. + return Provider.createInstance( + Class.forName( + "io.opencensus.impllite.trace.TraceComponentImplLite", + /*initialize=*/ true, + classLoader), + TraceComponent.class); + } catch (ClassNotFoundException e) { + logger.log( + Level.FINE, + "Couldn't load lite implementation for TraceComponent, now using " + + "default implementation for TraceComponent.", + e); + } + return TraceComponent.newNoopTraceComponent(); + } + + // No instance of this class. + private Tracing() {} +} diff --git a/api/src/main/java/io/opencensus/trace/config/TraceConfig.java b/api/src/main/java/io/opencensus/trace/config/TraceConfig.java new file mode 100644 index 00000000..ff701e20 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/config/TraceConfig.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.config; + +/** + * Global configuration of the trace service. This allows users to change configs for the default + * sampler, maximum events to be kept, etc. (see {@link TraceParams} for details). + * + * @since 0.5 + */ +public abstract class TraceConfig { + private static final NoopTraceConfig NOOP_TRACE_CONFIG = new NoopTraceConfig(); + + /** + * Returns the active {@code TraceParams}. + * + * @return the active {@code TraceParams}. + * @since 0.5 + */ + public abstract TraceParams getActiveTraceParams(); + + /** + * Updates the active {@link TraceParams}. + * + * @param traceParams the new active {@code TraceParams}. + * @since 0.5 + */ + public abstract void updateActiveTraceParams(TraceParams traceParams); + + /** + * Returns the no-op implementation of the {@code TraceConfig}. + * + * @return the no-op implementation of the {@code TraceConfig}. + * @since 0.5 + */ + public static TraceConfig getNoopTraceConfig() { + return NOOP_TRACE_CONFIG; + } + + private static final class NoopTraceConfig extends TraceConfig { + + @Override + public TraceParams getActiveTraceParams() { + return TraceParams.DEFAULT; + } + + @Override + public void updateActiveTraceParams(TraceParams traceParams) {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/config/TraceParams.java b/api/src/main/java/io/opencensus/trace/config/TraceParams.java new file mode 100644 index 00000000..ff70f52d --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/config/TraceParams.java @@ -0,0 +1,219 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.config; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.Utils; +import io.opencensus.trace.Annotation; +import io.opencensus.trace.Link; +import io.opencensus.trace.MessageEvent; +import io.opencensus.trace.Sampler; +import io.opencensus.trace.Span; +import io.opencensus.trace.samplers.Samplers; +import javax.annotation.concurrent.Immutable; + +/** + * Class that holds global trace parameters. + * + * @since 0.5 + */ +@AutoValue +@Immutable +public abstract class TraceParams { + // These values are the default values for all the global parameters. + private static final double DEFAULT_PROBABILITY = 1e-4; + private static final Sampler DEFAULT_SAMPLER = Samplers.probabilitySampler(DEFAULT_PROBABILITY); + private static final int DEFAULT_SPAN_MAX_NUM_ATTRIBUTES = 32; + private static final int DEFAULT_SPAN_MAX_NUM_ANNOTATIONS = 32; + private static final int DEFAULT_SPAN_MAX_NUM_MESSAGE_EVENTS = 128; + private static final int DEFAULT_SPAN_MAX_NUM_LINKS = 32; + + /** + * Default {@code TraceParams}. + * + * @since 0.5 + */ + public static final TraceParams DEFAULT = + TraceParams.builder() + .setSampler(DEFAULT_SAMPLER) + .setMaxNumberOfAttributes(DEFAULT_SPAN_MAX_NUM_ATTRIBUTES) + .setMaxNumberOfAnnotations(DEFAULT_SPAN_MAX_NUM_ANNOTATIONS) + .setMaxNumberOfMessageEvents(DEFAULT_SPAN_MAX_NUM_MESSAGE_EVENTS) + .setMaxNumberOfLinks(DEFAULT_SPAN_MAX_NUM_LINKS) + .build(); + + /** + * Returns the global default {@code Sampler}. Used if no {@code Sampler} is provided in {@link + * io.opencensus.trace.SpanBuilder#setSampler(Sampler)}. + * + * @return the global default {@code Sampler}. + * @since 0.5 + */ + public abstract Sampler getSampler(); + + /** + * Returns the global default max number of attributes per {@link Span}. + * + * @return the global default max number of attributes per {@link Span}. + * @since 0.5 + */ + public abstract int getMaxNumberOfAttributes(); + + /** + * Returns the global default max number of {@link Annotation} events per {@link Span}. + * + * @return the global default max number of {@code Annotation} events per {@code Span}. + * @since 0.5 + */ + public abstract int getMaxNumberOfAnnotations(); + + /** + * Returns the global default max number of {@link MessageEvent} events per {@link Span}. + * + * @return the global default max number of {@code MessageEvent} events per {@code Span}. + * @since 0.12 + */ + public abstract int getMaxNumberOfMessageEvents(); + + /** + * Returns the global default max number of {@link io.opencensus.trace.NetworkEvent} events per + * {@link Span}. + * + * @return the global default max number of {@code NetworkEvent} events per {@code Span}. + * @deprecated Use {@link getMaxNumberOfMessageEvents}. + * @since 0.5 + */ + @Deprecated + public int getMaxNumberOfNetworkEvents() { + return getMaxNumberOfMessageEvents(); + } + + /** + * Returns the global default max number of {@link Link} entries per {@link Span}. + * + * @return the global default max number of {@code Link} entries per {@code Span}. + * @since 0.5 + */ + public abstract int getMaxNumberOfLinks(); + + private static Builder builder() { + return new AutoValue_TraceParams.Builder(); + } + + /** + * Returns a {@link Builder} initialized to the same property values as the current instance. + * + * @return a {@link Builder} initialized to the same property values as the current instance. + * @since 0.5 + */ + public abstract Builder toBuilder(); + + /** + * A {@code Builder} class for {@link TraceParams}. + * + * @since 0.5 + */ + @AutoValue.Builder + public abstract static class Builder { + + /** + * Sets the global default {@code Sampler}. It must be not {@code null} otherwise {@link + * #build()} will throw an exception. + * + * @param sampler the global default {@code Sampler}. + * @return this. + * @since 0.5 + */ + public abstract Builder setSampler(Sampler sampler); + + /** + * Sets the global default max number of attributes per {@link Span}. + * + * @param maxNumberOfAttributes the global default max number of attributes per {@link Span}. It + * must be positive otherwise {@link #build()} will throw an exception. + * @return this. + * @since 0.5 + */ + public abstract Builder setMaxNumberOfAttributes(int maxNumberOfAttributes); + + /** + * Sets the global default max number of {@link Annotation} events per {@link Span}. + * + * @param maxNumberOfAnnotations the global default max number of {@link Annotation} events per + * {@link Span}. It must be positive otherwise {@link #build()} will throw an exception. + * @return this. + * @since 0.5 + */ + public abstract Builder setMaxNumberOfAnnotations(int maxNumberOfAnnotations); + + /** + * Sets the global default max number of {@link MessageEvent} events per {@link Span}. + * + * @param maxNumberOfMessageEvents the global default max number of {@link MessageEvent} events + * per {@link Span}. It must be positive otherwise {@link #build()} will throw an exception. + * @since 0.12 + * @return this. + */ + public abstract Builder setMaxNumberOfMessageEvents(int maxNumberOfMessageEvents); + + /** + * Sets the global default max number of {@link io.opencensus.trace.NetworkEvent} events per + * {@link Span}. + * + * @param maxNumberOfNetworkEvents the global default max number of {@link + * io.opencensus.trace.NetworkEvent} events per {@link Span}. It must be positive otherwise + * {@link #build()} will throw an exception. + * @return this. + * @deprecated Use {@link setMaxNumberOfMessageEvents}. + * @since 0.5 + */ + @Deprecated + public Builder setMaxNumberOfNetworkEvents(int maxNumberOfNetworkEvents) { + return setMaxNumberOfMessageEvents(maxNumberOfNetworkEvents); + } + + /** + * Sets the global default max number of {@link Link} entries per {@link Span}. + * + * @param maxNumberOfLinks the global default max number of {@link Link} entries per {@link + * Span}. It must be positive otherwise {@link #build()} will throw an exception. + * @return this. + * @since 0.5 + */ + public abstract Builder setMaxNumberOfLinks(int maxNumberOfLinks); + + abstract TraceParams autoBuild(); + + /** + * Builds and returns a {@code TraceParams} with the desired values. + * + * @return a {@code TraceParams} with the desired values. + * @throws NullPointerException if the sampler is {@code null}. + * @throws IllegalArgumentException if any of the max numbers are not positive. + * @since 0.5 + */ + public TraceParams build() { + TraceParams traceParams = autoBuild(); + Utils.checkArgument(traceParams.getMaxNumberOfAttributes() > 0, "maxNumberOfAttributes"); + Utils.checkArgument(traceParams.getMaxNumberOfAnnotations() > 0, "maxNumberOfAnnotations"); + Utils.checkArgument( + traceParams.getMaxNumberOfMessageEvents() > 0, "maxNumberOfMessageEvents"); + Utils.checkArgument(traceParams.getMaxNumberOfLinks() > 0, "maxNumberOfLinks"); + return traceParams; + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/export/ExportComponent.java b/api/src/main/java/io/opencensus/trace/export/ExportComponent.java new file mode 100644 index 00000000..c334c5a6 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/export/ExportComponent.java @@ -0,0 +1,95 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.export; + +import io.opencensus.trace.TraceOptions; + +/** + * Class that holds the implementation instances for {@link SpanExporter}, {@link RunningSpanStore} + * and {@link SampledSpanStore}. + * + * <p>Unless otherwise noted all methods (on component) results are cacheable. + * + * @since 0.5 + */ +public abstract class ExportComponent { + + /** + * Returns the no-op implementation of the {@code ExportComponent}. + * + * @return the no-op implementation of the {@code ExportComponent}. + * @since 0.5 + */ + public static ExportComponent newNoopExportComponent() { + return new NoopExportComponent(); + } + + /** + * Returns the {@link SpanExporter} which can be used to register handlers to export all the spans + * that are part of a distributed sampled trace (see {@link TraceOptions#isSampled()}). + * + * @return the implementation of the {@code SpanExporter} or no-op if no implementation linked in + * the binary. + * @since 0.5 + */ + public abstract SpanExporter getSpanExporter(); + + /** + * Returns the {@link RunningSpanStore} that can be used to get useful debugging information about + * all the current active spans. + * + * @return the {@code RunningSpanStore}. + * @since 0.5 + */ + public abstract RunningSpanStore getRunningSpanStore(); + + /** + * Returns the {@link SampledSpanStore} that can be used to get useful debugging information, such + * as latency based sampled spans, error based sampled spans. + * + * @return the {@code SampledSpanStore}. + * @since 0.5 + */ + public abstract SampledSpanStore getSampledSpanStore(); + + /** + * Will shutdown this ExportComponent after flushing any pending spans. + * + * @since 0.14 + */ + public void shutdown() {} + + private static final class NoopExportComponent extends ExportComponent { + private final SampledSpanStore noopSampledSpanStore = + SampledSpanStore.newNoopSampledSpanStore(); + + @Override + public SpanExporter getSpanExporter() { + return SpanExporter.getNoopSpanExporter(); + } + + @Override + public RunningSpanStore getRunningSpanStore() { + return RunningSpanStore.getNoopRunningSpanStore(); + } + + @Override + public SampledSpanStore getSampledSpanStore() { + return noopSampledSpanStore; + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/export/RunningSpanStore.java b/api/src/main/java/io/opencensus/trace/export/RunningSpanStore.java new file mode 100644 index 00000000..fac3c855 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/export/RunningSpanStore.java @@ -0,0 +1,201 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.Utils; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * This class allows users to access in-process information about all running spans. + * + * <p>The running spans tracking is available for all the spans with the option {@link + * io.opencensus.trace.Span.Options#RECORD_EVENTS}. This functionality allows users to debug stuck + * operations or long living operations. + * + * @since 0.5 + */ +@ThreadSafe +public abstract class RunningSpanStore { + + private static final RunningSpanStore NOOP_RUNNING_SPAN_STORE = new NoopRunningSpanStore(); + + protected RunningSpanStore() {} + + /** + * Returns the no-op implementation of the {@code RunningSpanStore}. + * + * @return the no-op implementation of the {@code RunningSpanStore}. + */ + static RunningSpanStore getNoopRunningSpanStore() { + return NOOP_RUNNING_SPAN_STORE; + } + + /** + * Returns the summary of all available data such, as number of running spans. + * + * @return the summary of all available data. + * @since 0.5 + */ + public abstract Summary getSummary(); + + /** + * Returns a list of running spans that match the {@code Filter}. + * + * @param filter used to filter the returned spans. + * @return a list of running spans that match the {@code Filter}. + * @since 0.5 + */ + public abstract Collection<SpanData> getRunningSpans(Filter filter); + + /** + * The summary of all available data. + * + * @since 0.5 + */ + @AutoValue + @Immutable + public abstract static class Summary { + + Summary() {} + + /** + * Returns a new instance of {@code Summary}. + * + * @param perSpanNameSummary a map with summary for each span name. + * @return a new instance of {@code Summary}. + * @throws NullPointerException if {@code perSpanNameSummary} is {@code null}. + * @since 0.5 + */ + public static Summary create(Map<String, PerSpanNameSummary> perSpanNameSummary) { + return new AutoValue_RunningSpanStore_Summary( + Collections.unmodifiableMap( + new HashMap<String, PerSpanNameSummary>( + Utils.checkNotNull(perSpanNameSummary, "perSpanNameSummary")))); + } + + /** + * Returns a map with summary of available data for each span name. + * + * @return a map with all the span names and the summary. + * @since 0.5 + */ + public abstract Map<String, PerSpanNameSummary> getPerSpanNameSummary(); + } + + /** + * Summary of all available data for a span name. + * + * @since 0.5 + */ + @AutoValue + @Immutable + public abstract static class PerSpanNameSummary { + + PerSpanNameSummary() {} + + /** + * Returns a new instance of {@code PerSpanNameSummary}. + * + * @param numRunningSpans the number of running spans. + * @return a new instance of {@code PerSpanNameSummary}. + * @throws IllegalArgumentException if {@code numRunningSpans} is negative. + * @since 0.5 + */ + public static PerSpanNameSummary create(int numRunningSpans) { + Utils.checkArgument(numRunningSpans >= 0, "Negative numRunningSpans."); + return new AutoValue_RunningSpanStore_PerSpanNameSummary(numRunningSpans); + } + + /** + * Returns the number of running spans. + * + * @return the number of running spans. + * @since 0.5 + */ + public abstract int getNumRunningSpans(); + } + + /** + * Filter for running spans. Used to filter results returned by the {@link + * #getRunningSpans(Filter)} request. + * + * @since 0.5 + */ + @AutoValue + @Immutable + public abstract static class Filter { + + Filter() {} + + /** + * Returns a new instance of {@code Filter}. + * + * <p>Filters all the spans based on {@code spanName} and returns a maximum of {@code + * maxSpansToReturn}. + * + * @param spanName the name of the span. + * @param maxSpansToReturn the maximum number of results to be returned. {@code 0} means all. + * @return a new instance of {@code Filter}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @throws IllegalArgumentException if {@code maxSpansToReturn} is negative. + * @since 0.5 + */ + public static Filter create(String spanName, int maxSpansToReturn) { + Utils.checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn."); + return new AutoValue_RunningSpanStore_Filter(spanName, maxSpansToReturn); + } + + /** + * Returns the span name. + * + * @return the span name. + * @since 0.5 + */ + public abstract String getSpanName(); + + /** + * Returns the maximum number of spans to be returned. {@code 0} means all. + * + * @return the maximum number of spans to be returned. + * @since 0.5 + */ + public abstract int getMaxSpansToReturn(); + } + + private static final class NoopRunningSpanStore extends RunningSpanStore { + + private static final Summary EMPTY_SUMMARY = + Summary.create(Collections.<String, PerSpanNameSummary>emptyMap()); + + @Override + public Summary getSummary() { + return EMPTY_SUMMARY; + } + + @Override + public Collection<SpanData> getRunningSpans(Filter filter) { + Utils.checkNotNull(filter, "filter"); + return Collections.<SpanData>emptyList(); + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/export/SampledSpanStore.java b/api/src/main/java/io/opencensus/trace/export/SampledSpanStore.java new file mode 100644 index 00000000..5d00a45d --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/export/SampledSpanStore.java @@ -0,0 +1,525 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.Utils; +import io.opencensus.trace.Span; +import io.opencensus.trace.Status; +import io.opencensus.trace.Status.CanonicalCode; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * This class allows users to access in-process information such as latency based sampled spans and + * error based sampled spans. + * + * <p>For all completed spans with the option {@link Span.Options#RECORD_EVENTS} the library can + * store samples based on latency for succeeded operations or based on error code for failed + * operations. To activate this, users MUST manually configure all the span names for which samples + * will be collected (see {@link #registerSpanNamesForCollection(Collection)}). + * + * @since 0.5 + */ +@ThreadSafe +public abstract class SampledSpanStore { + + protected SampledSpanStore() {} + + /** + * Returns a {@code SampledSpanStore} that maintains a set of span names, but always returns an + * empty list of {@link SpanData}. + * + * @return a {@code SampledSpanStore} that maintains a set of span names, but always returns an + * empty list of {@code SpanData}. + */ + static SampledSpanStore newNoopSampledSpanStore() { + return new NoopSampledSpanStore(); + } + + /** + * Returns the summary of all available data, such as number of sampled spans in the latency based + * samples or error based samples. + * + * <p>Data available only for span names registered using {@link + * #registerSpanNamesForCollection(Collection)}. + * + * @return the summary of all available data. + * @since 0.5 + */ + public abstract Summary getSummary(); + + /** + * Returns a list of succeeded spans (spans with {@link Status} equal to {@link Status#OK}) that + * match the {@code filter}. + * + * <p>Latency based sampled spans are available only for span names registered using {@link + * #registerSpanNamesForCollection(Collection)}. + * + * @param filter used to filter the returned sampled spans. + * @return a list of succeeded spans that match the {@code filter}. + * @since 0.5 + */ + public abstract Collection<SpanData> getLatencySampledSpans(LatencyFilter filter); + + /** + * Returns a list of failed spans (spans with {@link Status} other than {@link Status#OK}) that + * match the {@code filter}. + * + * <p>Error based sampled spans are available only for span names registered using {@link + * #registerSpanNamesForCollection(Collection)}. + * + * @param filter used to filter the returned sampled spans. + * @return a list of failed spans that match the {@code filter}. + * @since 0.5 + */ + public abstract Collection<SpanData> getErrorSampledSpans(ErrorFilter filter); + + /** + * Appends a list of span names for which the library will collect latency based sampled spans and + * error based sampled spans. + * + * <p>If called multiple times the library keeps the list of unique span names from all the calls. + * + * @param spanNames list of span names for which the library will collect samples. + * @since 0.5 + */ + public abstract void registerSpanNamesForCollection(Collection<String> spanNames); + + /** + * Removes a list of span names for which the library will collect latency based sampled spans and + * error based sampled spans. + * + * <p>The library keeps the list of unique registered span names for which samples will be called. + * This method allows users to remove span names from that list. + * + * @param spanNames list of span names for which the library will no longer collect samples. + * @since 0.5 + */ + public abstract void unregisterSpanNamesForCollection(Collection<String> spanNames); + + /** + * Returns the set of unique span names registered to the library, for use in tests. For this set + * of span names the library will collect latency based sampled spans and error based sampled + * spans. + * + * <p>This method is only meant for testing code that uses OpenCensus, and it is not performant. + * + * @return the set of unique span names registered to the library. + * @since 0.7 + */ + public abstract Set<String> getRegisteredSpanNamesForCollection(); + + /** + * The summary of all available data. + * + * @since 0.5 + */ + @AutoValue + @Immutable + public abstract static class Summary { + + Summary() {} + + /** + * Returns a new instance of {@code Summary}. + * + * @param perSpanNameSummary a map with summary for each span name. + * @return a new instance of {@code Summary}. + * @throws NullPointerException if {@code perSpanNameSummary} is {@code null}. + * @since 0.5 + */ + public static Summary create(Map<String, PerSpanNameSummary> perSpanNameSummary) { + return new AutoValue_SampledSpanStore_Summary( + Collections.unmodifiableMap( + new HashMap<String, PerSpanNameSummary>( + Utils.checkNotNull(perSpanNameSummary, "perSpanNameSummary")))); + } + + /** + * Returns a map with summary of available data for each span name. + * + * @return a map with all the span names and the summary. + * @since 0.5 + */ + public abstract Map<String, PerSpanNameSummary> getPerSpanNameSummary(); + } + + /** + * Summary of all available data for a span name. + * + * @since 0.5 + */ + @AutoValue + @Immutable + public abstract static class PerSpanNameSummary { + + PerSpanNameSummary() {} + + /** + * Returns a new instance of {@code PerSpanNameSummary}. + * + * @param numbersOfLatencySampledSpans the summary for the latency buckets. + * @param numbersOfErrorSampledSpans the summary for the error buckets. + * @return a new instance of {@code PerSpanNameSummary}. + * @throws NullPointerException if {@code numbersOfLatencySampledSpans} or {@code + * numbersOfErrorSampledSpans} are {@code null}. + * @since 0.5 + */ + public static PerSpanNameSummary create( + Map<LatencyBucketBoundaries, Integer> numbersOfLatencySampledSpans, + Map<CanonicalCode, Integer> numbersOfErrorSampledSpans) { + return new AutoValue_SampledSpanStore_PerSpanNameSummary( + Collections.unmodifiableMap( + new HashMap<LatencyBucketBoundaries, Integer>( + Utils.checkNotNull( + numbersOfLatencySampledSpans, "numbersOfLatencySampledSpans"))), + Collections.unmodifiableMap( + new HashMap<CanonicalCode, Integer>( + Utils.checkNotNull(numbersOfErrorSampledSpans, "numbersOfErrorSampledSpans")))); + } + + /** + * Returns the number of sampled spans in all the latency buckets. + * + * <p>Data available only for span names registered using {@link + * #registerSpanNamesForCollection(Collection)}. + * + * @return the number of sampled spans in all the latency buckets. + * @since 0.5 + */ + public abstract Map<LatencyBucketBoundaries, Integer> getNumbersOfLatencySampledSpans(); + + /** + * Returns the number of sampled spans in all the error buckets. + * + * <p>Data available only for span names registered using {@link + * #registerSpanNamesForCollection(Collection)}. + * + * @return the number of sampled spans in all the error buckets. + * @since 0.5 + */ + public abstract Map<CanonicalCode, Integer> getNumbersOfErrorSampledSpans(); + } + + /** + * The latency buckets boundaries. Samples based on latency for successful spans (the status of + * the span has a canonical code equal to {@link CanonicalCode#OK}) are collected in one of these + * latency buckets. + * + * @since 0.5 + */ + public enum LatencyBucketBoundaries { + /** + * Stores finished successful requests of duration within the interval [0, 10us). + * + * @since 0.5 + */ + ZERO_MICROSx10(0, TimeUnit.MICROSECONDS.toNanos(10)), + + /** + * Stores finished successful requests of duration within the interval [10us, 100us). + * + * @since 0.5 + */ + MICROSx10_MICROSx100(TimeUnit.MICROSECONDS.toNanos(10), TimeUnit.MICROSECONDS.toNanos(100)), + + /** + * Stores finished successful requests of duration within the interval [100us, 1ms). + * + * @since 0.5 + */ + MICROSx100_MILLIx1(TimeUnit.MICROSECONDS.toNanos(100), TimeUnit.MILLISECONDS.toNanos(1)), + + /** + * Stores finished successful requests of duration within the interval [1ms, 10ms). + * + * @since 0.5 + */ + MILLIx1_MILLIx10(TimeUnit.MILLISECONDS.toNanos(1), TimeUnit.MILLISECONDS.toNanos(10)), + + /** + * Stores finished successful requests of duration within the interval [10ms, 100ms). + * + * @since 0.5 + */ + MILLIx10_MILLIx100(TimeUnit.MILLISECONDS.toNanos(10), TimeUnit.MILLISECONDS.toNanos(100)), + + /** + * Stores finished successful requests of duration within the interval [100ms, 1sec). + * + * @since 0.5 + */ + MILLIx100_SECONDx1(TimeUnit.MILLISECONDS.toNanos(100), TimeUnit.SECONDS.toNanos(1)), + + /** + * Stores finished successful requests of duration within the interval [1sec, 10sec). + * + * @since 0.5 + */ + SECONDx1_SECONDx10(TimeUnit.SECONDS.toNanos(1), TimeUnit.SECONDS.toNanos(10)), + + /** + * Stores finished successful requests of duration within the interval [10sec, 100sec). + * + * @since 0.5 + */ + SECONDx10_SECONDx100(TimeUnit.SECONDS.toNanos(10), TimeUnit.SECONDS.toNanos(100)), + + /** + * Stores finished successful requests of duration >= 100sec. + * + * @since 0.5 + */ + SECONDx100_MAX(TimeUnit.SECONDS.toNanos(100), Long.MAX_VALUE); + + /** + * Constructs a {@code LatencyBucketBoundaries} with the given boundaries and label. + * + * @param latencyLowerNs the latency lower bound of the bucket. + * @param latencyUpperNs the latency upper bound of the bucket. + */ + LatencyBucketBoundaries(long latencyLowerNs, long latencyUpperNs) { + this.latencyLowerNs = latencyLowerNs; + this.latencyUpperNs = latencyUpperNs; + } + + /** + * Returns the latency lower bound of the bucket. + * + * @return the latency lower bound of the bucket. + * @since 0.5 + */ + public long getLatencyLowerNs() { + return latencyLowerNs; + } + + /** + * Returns the latency upper bound of the bucket. + * + * @return the latency upper bound of the bucket. + * @since 0.5 + */ + public long getLatencyUpperNs() { + return latencyUpperNs; + } + + private final long latencyLowerNs; + private final long latencyUpperNs; + } + + /** + * Filter for latency based sampled spans. Used to filter results returned by the {@link + * #getLatencySampledSpans(LatencyFilter)} request. + * + * @since 0.5 + */ + @AutoValue + @Immutable + public abstract static class LatencyFilter { + + LatencyFilter() {} + + /** + * Returns a new instance of {@code LatencyFilter}. + * + * <p>Filters all the spans based on {@code spanName} and latency in the interval + * [latencyLowerNs, latencyUpperNs) and returns a maximum of {@code maxSpansToReturn}. + * + * @param spanName the name of the span. + * @param latencyLowerNs the latency lower bound. + * @param latencyUpperNs the latency upper bound. + * @param maxSpansToReturn the maximum number of results to be returned. {@code 0} means all. + * @return a new instance of {@code LatencyFilter}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @throws IllegalArgumentException if {@code maxSpansToReturn} or {@code latencyLowerNs} or + * {@code latencyUpperNs} are negative. + * @since 0.5 + */ + public static LatencyFilter create( + String spanName, long latencyLowerNs, long latencyUpperNs, int maxSpansToReturn) { + Utils.checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn."); + Utils.checkArgument(latencyLowerNs >= 0, "Negative latencyLowerNs"); + Utils.checkArgument(latencyUpperNs >= 0, "Negative latencyUpperNs"); + return new AutoValue_SampledSpanStore_LatencyFilter( + spanName, latencyLowerNs, latencyUpperNs, maxSpansToReturn); + } + + /** + * Returns the span name used by this filter. + * + * @return the span name used by this filter. + * @since 0.5 + */ + public abstract String getSpanName(); + + /** + * Returns the latency lower bound of this bucket (inclusive). + * + * @return the latency lower bound of this bucket. + * @since 0.5 + */ + public abstract long getLatencyLowerNs(); + + /** + * Returns the latency upper bound of this bucket (exclusive). + * + * @return the latency upper bound of this bucket. + * @since 0.5 + */ + public abstract long getLatencyUpperNs(); + + /** + * Returns the maximum number of spans to be returned. {@code 0} means all. + * + * @return the maximum number of spans to be returned. + * @since 0.5 + */ + public abstract int getMaxSpansToReturn(); + } + + /** + * Filter for error based sampled spans. Used to filter results returned by the {@link + * #getErrorSampledSpans(ErrorFilter)} request. + * + * @since 0.5 + */ + @AutoValue + @Immutable + public abstract static class ErrorFilter { + + ErrorFilter() {} + + /** + * Returns a new instance of {@code ErrorFilter}. + * + * <p>Filters all the spans based on {@code spanName} and {@code canonicalCode} and returns a + * maximum of {@code maxSpansToReturn}. + * + * @param spanName the name of the span. + * @param canonicalCode the error code of the span. {@code null} can be used to query all error + * codes. + * @param maxSpansToReturn the maximum number of results to be returned. {@code 0} means all. + * @return a new instance of {@code ErrorFilter}. + * @throws NullPointerException if {@code spanName} is {@code null}. + * @throws IllegalArgumentException if {@code canonicalCode} is {@link CanonicalCode#OK} or + * {@code maxSpansToReturn} is negative. + * @since 0.5 + */ + public static ErrorFilter create( + String spanName, @Nullable CanonicalCode canonicalCode, int maxSpansToReturn) { + if (canonicalCode != null) { + Utils.checkArgument(canonicalCode != CanonicalCode.OK, "Invalid canonical code."); + } + Utils.checkArgument(maxSpansToReturn >= 0, "Negative maxSpansToReturn."); + return new AutoValue_SampledSpanStore_ErrorFilter(spanName, canonicalCode, maxSpansToReturn); + } + + /** + * Returns the span name used by this filter. + * + * @return the span name used by this filter. + * @since 0.5 + */ + public abstract String getSpanName(); + + /** + * Returns the canonical code used by this filter. Always different than {@link + * CanonicalCode#OK}. If {@code null} then all errors match. + * + * @return the canonical code used by this filter. + * @since 0.5 + */ + @Nullable + public abstract CanonicalCode getCanonicalCode(); + + /** + * Returns the maximum number of spans to be returned. Used to enforce the number of returned + * {@code SpanData}. {@code 0} means all. + * + * @return the maximum number of spans to be returned. + * @since 0.5 + */ + public abstract int getMaxSpansToReturn(); + } + + @ThreadSafe + private static final class NoopSampledSpanStore extends SampledSpanStore { + private static final PerSpanNameSummary EMPTY_PER_SPAN_NAME_SUMMARY = + PerSpanNameSummary.create( + Collections.<SampledSpanStore.LatencyBucketBoundaries, Integer>emptyMap(), + Collections.<CanonicalCode, Integer>emptyMap()); + + @GuardedBy("registeredSpanNames") + private final Set<String> registeredSpanNames = new HashSet<String>(); + + @Override + public Summary getSummary() { + Map<String, PerSpanNameSummary> result = new HashMap<String, PerSpanNameSummary>(); + synchronized (registeredSpanNames) { + for (String registeredSpanName : registeredSpanNames) { + result.put(registeredSpanName, EMPTY_PER_SPAN_NAME_SUMMARY); + } + } + return Summary.create(result); + } + + @Override + public Collection<SpanData> getLatencySampledSpans(LatencyFilter filter) { + Utils.checkNotNull(filter, "latencyFilter"); + return Collections.<SpanData>emptyList(); + } + + @Override + public Collection<SpanData> getErrorSampledSpans(ErrorFilter filter) { + Utils.checkNotNull(filter, "errorFilter"); + return Collections.<SpanData>emptyList(); + } + + @Override + public void registerSpanNamesForCollection(Collection<String> spanNames) { + Utils.checkNotNull(spanNames, "spanNames"); + synchronized (registeredSpanNames) { + registeredSpanNames.addAll(spanNames); + } + } + + @Override + public void unregisterSpanNamesForCollection(Collection<String> spanNames) { + Utils.checkNotNull(spanNames, "spanNames"); + synchronized (registeredSpanNames) { + registeredSpanNames.removeAll(spanNames); + } + } + + @Override + public Set<String> getRegisteredSpanNamesForCollection() { + synchronized (registeredSpanNames) { + return Collections.<String>unmodifiableSet(new HashSet<String>(registeredSpanNames)); + } + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/export/SpanData.java b/api/src/main/java/io/opencensus/trace/export/SpanData.java new file mode 100644 index 00000000..f4dd4682 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/export/SpanData.java @@ -0,0 +1,477 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.export; + +import com.google.auto.value.AutoValue; +import io.opencensus.common.Timestamp; +import io.opencensus.internal.Utils; +import io.opencensus.trace.Annotation; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.Link; +import io.opencensus.trace.MessageEvent; +import io.opencensus.trace.Span; +import io.opencensus.trace.Span.Kind; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.Status; +import io.opencensus.trace.internal.BaseMessageEventUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/*>>> +import org.checkerframework.dataflow.qual.Deterministic; +*/ + +/** + * Immutable representation of all data collected by the {@link Span} class. + * + * @since 0.5 + */ +@Immutable +@AutoValue +public abstract class SpanData { + + /** + * Returns a new immutable {@code SpanData}. + * + * @deprecated Use {@link #create(SpanContext, SpanId, Boolean, String, Kind, Timestamp, + * Attributes, TimedEvents, TimedEvents, Links, Integer, Status, Timestamp)}. + */ + @Deprecated + public static SpanData create( + SpanContext context, + @Nullable SpanId parentSpanId, + @Nullable Boolean hasRemoteParent, + String name, + Timestamp startTimestamp, + Attributes attributes, + TimedEvents<Annotation> annotations, + TimedEvents<? extends io.opencensus.trace.BaseMessageEvent> messageOrNetworkEvents, + Links links, + @Nullable Integer childSpanCount, + @Nullable Status status, + @Nullable Timestamp endTimestamp) { + return create( + context, + parentSpanId, + hasRemoteParent, + name, + null, + startTimestamp, + attributes, + annotations, + messageOrNetworkEvents, + links, + childSpanCount, + status, + endTimestamp); + } + + /** + * Returns a new immutable {@code SpanData}. + * + * @param context the {@code SpanContext} of the {@code Span}. + * @param parentSpanId the parent {@code SpanId} of the {@code Span}. {@code null} if the {@code + * Span} is a root. + * @param hasRemoteParent {@code true} if the parent {@code Span} is remote. {@code null} if this + * is a root span. + * @param name the name of the {@code Span}. + * @param kind the kind of the {@code Span}. + * @param startTimestamp the start {@code Timestamp} of the {@code Span}. + * @param attributes the attributes associated with the {@code Span}. + * @param annotations the annotations associated with the {@code Span}. + * @param messageOrNetworkEvents the message events (or network events for backward compatibility) + * associated with the {@code Span}. + * @param links the links associated with the {@code Span}. + * @param childSpanCount the number of child spans that were generated while the span was active. + * @param status the {@code Status} of the {@code Span}. {@code null} if the {@code Span} is still + * active. + * @param endTimestamp the end {@code Timestamp} of the {@code Span}. {@code null} if the {@code + * Span} is still active. + * @return a new immutable {@code SpanData}. + * @since 0.14 + */ + @SuppressWarnings({"deprecation", "InconsistentOverloads"}) + public static SpanData create( + SpanContext context, + @Nullable SpanId parentSpanId, + @Nullable Boolean hasRemoteParent, + String name, + @Nullable Kind kind, + Timestamp startTimestamp, + Attributes attributes, + TimedEvents<Annotation> annotations, + TimedEvents<? extends io.opencensus.trace.BaseMessageEvent> messageOrNetworkEvents, + Links links, + @Nullable Integer childSpanCount, + @Nullable Status status, + @Nullable Timestamp endTimestamp) { + Utils.checkNotNull(messageOrNetworkEvents, "messageOrNetworkEvents"); + List<TimedEvent<MessageEvent>> messageEventsList = new ArrayList<TimedEvent<MessageEvent>>(); + for (TimedEvent<? extends io.opencensus.trace.BaseMessageEvent> timedEvent : + messageOrNetworkEvents.getEvents()) { + io.opencensus.trace.BaseMessageEvent event = timedEvent.getEvent(); + if (event instanceof MessageEvent) { + @SuppressWarnings("unchecked") + TimedEvent<MessageEvent> timedMessageEvent = (TimedEvent<MessageEvent>) timedEvent; + messageEventsList.add(timedMessageEvent); + } else { + messageEventsList.add( + TimedEvent.<MessageEvent>create( + timedEvent.getTimestamp(), BaseMessageEventUtils.asMessageEvent(event))); + } + } + TimedEvents<MessageEvent> messageEvents = + TimedEvents.<MessageEvent>create( + messageEventsList, messageOrNetworkEvents.getDroppedEventsCount()); + return new AutoValue_SpanData( + context, + parentSpanId, + hasRemoteParent, + name, + kind, + startTimestamp, + attributes, + annotations, + messageEvents, + links, + childSpanCount, + status, + endTimestamp); + } + + /** + * Returns the {@code SpanContext} associated with this {@code Span}. + * + * @return the {@code SpanContext} associated with this {@code Span}. + * @since 0.5 + */ + public abstract SpanContext getContext(); + + /** + * Returns the parent {@code SpanId} or {@code null} if the {@code Span} is a root {@code Span}. + * + * @return the parent {@code SpanId} or {@code null} if the {@code Span} is a root {@code Span}. + * @since 0.5 + */ + @Nullable + /*@Deterministic*/ + public abstract SpanId getParentSpanId(); + + /** + * Returns {@code true} if the parent is on a different process. {@code null} if this is a root + * span. + * + * @return {@code true} if the parent is on a different process. {@code null} if this is a root + * span. + * @since 0.5 + */ + @Nullable + public abstract Boolean getHasRemoteParent(); + + /** + * Returns the name of this {@code Span}. + * + * @return the name of this {@code Span}. + * @since 0.5 + */ + public abstract String getName(); + + /** + * Returns the kind of this {@code Span}. + * + * @return the kind of this {@code Span}. + * @since 0.14 + */ + @Nullable + public abstract Kind getKind(); + + /** + * Returns the start {@code Timestamp} of this {@code Span}. + * + * @return the start {@code Timestamp} of this {@code Span}. + * @since 0.5 + */ + public abstract Timestamp getStartTimestamp(); + + /** + * Returns the attributes recorded for this {@code Span}. + * + * @return the attributes recorded for this {@code Span}. + * @since 0.5 + */ + public abstract Attributes getAttributes(); + + /** + * Returns the annotations recorded for this {@code Span}. + * + * @return the annotations recorded for this {@code Span}. + * @since 0.5 + */ + public abstract TimedEvents<Annotation> getAnnotations(); + + /** + * Returns network events recorded for this {@code Span}. + * + * @return network events recorded for this {@code Span}. + * @deprecated Use {@link #getMessageEvents}. + * @since 0.5 + */ + @Deprecated + @SuppressWarnings({"deprecation"}) + public TimedEvents<io.opencensus.trace.NetworkEvent> getNetworkEvents() { + TimedEvents<MessageEvent> timedEvents = getMessageEvents(); + List<TimedEvent<io.opencensus.trace.NetworkEvent>> networkEventsList = + new ArrayList<TimedEvent<io.opencensus.trace.NetworkEvent>>(); + for (TimedEvent<MessageEvent> timedEvent : timedEvents.getEvents()) { + networkEventsList.add( + TimedEvent.<io.opencensus.trace.NetworkEvent>create( + timedEvent.getTimestamp(), + BaseMessageEventUtils.asNetworkEvent(timedEvent.getEvent()))); + } + return TimedEvents.<io.opencensus.trace.NetworkEvent>create( + networkEventsList, timedEvents.getDroppedEventsCount()); + } + + /** + * Returns message events recorded for this {@code Span}. + * + * @return message events recorded for this {@code Span}. + * @since 0.12 + */ + public abstract TimedEvents<MessageEvent> getMessageEvents(); + + /** + * Returns links recorded for this {@code Span}. + * + * @return links recorded for this {@code Span}. + * @since 0.5 + */ + public abstract Links getLinks(); + + /** + * Returns the number of child spans that were generated while the {@code Span} was running. If + * not {@code null} allows service implementations to detect missing child spans. + * + * <p>This information is not always available. + * + * @return the number of child spans that were generated while the {@code Span} was running. + * @since 0.5 + */ + @Nullable + public abstract Integer getChildSpanCount(); + + /** + * Returns the {@code Status} or {@code null} if {@code Span} is still active. + * + * @return the {@code Status} or {@code null} if {@code Span} is still active. + * @since 0.5 + */ + @Nullable + /*@Deterministic*/ + public abstract Status getStatus(); + + /** + * Returns the end {@code Timestamp} or {@code null} if the {@code Span} is still active. + * + * @return the end {@code Timestamp} or {@code null} if the {@code Span} is still active. + * @since 0.5 + */ + @Nullable + /*@Deterministic*/ + public abstract Timestamp getEndTimestamp(); + + SpanData() {} + + /** + * A timed event representation. + * + * @param <T> the type of value that is timed. + * @since 0.5 + */ + @Immutable + @AutoValue + public abstract static class TimedEvent<T> { + /** + * Returns a new immutable {@code TimedEvent<T>}. + * + * @param timestamp the {@code Timestamp} of this event. + * @param event the event. + * @param <T> the type of value that is timed. + * @return a new immutable {@code TimedEvent<T>} + * @since 0.5 + */ + public static <T> TimedEvent<T> create(Timestamp timestamp, T event) { + return new AutoValue_SpanData_TimedEvent<T>(timestamp, event); + } + + /** + * Returns the {@code Timestamp} of this event. + * + * @return the {@code Timestamp} of this event. + * @since 0.5 + */ + public abstract Timestamp getTimestamp(); + + /** + * Returns the event. + * + * @return the event. + * @since 0.5 + */ + /*@Deterministic*/ + public abstract T getEvent(); + + TimedEvent() {} + } + + /** + * A list of timed events and the number of dropped events representation. + * + * @param <T> the type of value that is timed. + * @since 0.5 + */ + @Immutable + @AutoValue + public abstract static class TimedEvents<T> { + /** + * Returns a new immutable {@code TimedEvents<T>}. + * + * @param events the list of events. + * @param droppedEventsCount the number of dropped events. + * @param <T> the type of value that is timed. + * @return a new immutable {@code TimedEvents<T>} + * @since 0.5 + */ + public static <T> TimedEvents<T> create(List<TimedEvent<T>> events, int droppedEventsCount) { + return new AutoValue_SpanData_TimedEvents<T>( + Collections.unmodifiableList( + new ArrayList<TimedEvent<T>>(Utils.checkNotNull(events, "events"))), + droppedEventsCount); + } + + /** + * Returns the list of events. + * + * @return the list of events. + * @since 0.5 + */ + public abstract List<TimedEvent<T>> getEvents(); + + /** + * Returns the number of dropped events. + * + * @return the number of dropped events. + * @since 0.5 + */ + public abstract int getDroppedEventsCount(); + + TimedEvents() {} + } + + /** + * A set of attributes and the number of dropped attributes representation. + * + * @since 0.5 + */ + @Immutable + @AutoValue + public abstract static class Attributes { + /** + * Returns a new immutable {@code Attributes}. + * + * @param attributeMap the set of attributes. + * @param droppedAttributesCount the number of dropped attributes. + * @return a new immutable {@code Attributes}. + * @since 0.5 + */ + public static Attributes create( + Map<String, AttributeValue> attributeMap, int droppedAttributesCount) { + // TODO(bdrutu): Consider to use LinkedHashMap here and everywhere else, less test flakes + // for others on account of determinism. + return new AutoValue_SpanData_Attributes( + Collections.unmodifiableMap( + new HashMap<String, AttributeValue>( + Utils.checkNotNull(attributeMap, "attributeMap"))), + droppedAttributesCount); + } + + /** + * Returns the set of attributes. + * + * @return the set of attributes. + * @since 0.5 + */ + public abstract Map<String, AttributeValue> getAttributeMap(); + + /** + * Returns the number of dropped attributes. + * + * @return the number of dropped attributes. + * @since 0.5 + */ + public abstract int getDroppedAttributesCount(); + + Attributes() {} + } + + /** + * A list of links and the number of dropped links representation. + * + * @since 0.5 + */ + @Immutable + @AutoValue + public abstract static class Links { + /** + * Returns a new immutable {@code Links}. + * + * @param links the list of links. + * @param droppedLinksCount the number of dropped links. + * @return a new immutable {@code Links}. + * @since 0.5 + */ + public static Links create(List<Link> links, int droppedLinksCount) { + return new AutoValue_SpanData_Links( + Collections.unmodifiableList(new ArrayList<Link>(Utils.checkNotNull(links, "links"))), + droppedLinksCount); + } + + /** + * Returns the list of links. + * + * @return the list of links. + * @since 0.5 + */ + public abstract List<Link> getLinks(); + + /** + * Returns the number of dropped links. + * + * @return the number of dropped links. + * @since 0.5 + */ + public abstract int getDroppedLinksCount(); + + Links() {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/export/SpanExporter.java b/api/src/main/java/io/opencensus/trace/export/SpanExporter.java new file mode 100644 index 00000000..73ac5265 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/export/SpanExporter.java @@ -0,0 +1,96 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.export; + +import io.opencensus.trace.Span; +import io.opencensus.trace.TraceOptions; +import java.util.Collection; +import javax.annotation.concurrent.ThreadSafe; + +/** + * A service that is used by the library to export {@code SpanData} for all the spans that are part + * of a distributed sampled trace (see {@link TraceOptions#isSampled()}). + * + * @since 0.5 + */ +@ThreadSafe +public abstract class SpanExporter { + private static final SpanExporter NOOP_SPAN_EXPORTER = new NoopSpanExporter(); + + /** + * Returns the no-op implementation of the {@code ExportComponent}. + * + * @return the no-op implementation of the {@code ExportComponent}. + * @since 0.5 + */ + public static SpanExporter getNoopSpanExporter() { + return NOOP_SPAN_EXPORTER; + } + + /** + * Registers a new service handler that is used by the library to export {@code SpanData} for + * sampled spans (see {@link TraceOptions#isSampled()}). + * + * @param name the name of the service handler. Must be unique for each service. + * @param handler the service handler that is called for each ended sampled span. + * @since 0.5 + */ + public abstract void registerHandler(String name, Handler handler); + + /** + * Unregisters the service handler with the provided name. + * + * @param name the name of the service handler that will be unregistered. + * @since 0.5 + */ + public abstract void unregisterHandler(String name); + + /** + * An abstract class that allows different tracing services to export recorded data for sampled + * spans in their own format. + * + * <p>To export data this MUST be register to to the ExportComponent using {@link + * #registerHandler(String, Handler)}. + * + * @since 0.5 + */ + public abstract static class Handler { + + /** + * Exports a list of sampled (see {@link TraceOptions#isSampled()}) {@link Span}s using the + * immutable representation {@link SpanData}. + * + * <p>This may be called from a different thread than the one that called {@link Span#end()}. + * + * <p>Implementation SHOULD not block the calling thread. It should execute the export on a + * different thread if possible. + * + * @param spanDataList a list of {@code SpanData} objects to be exported. + * @since 0.5 + */ + public abstract void export(Collection<SpanData> spanDataList); + } + + private static final class NoopSpanExporter extends SpanExporter { + + @Override + public void registerHandler(String name, Handler handler) {} + + @Override + public void unregisterHandler(String name) {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/internal/BaseMessageEventUtils.java b/api/src/main/java/io/opencensus/trace/internal/BaseMessageEventUtils.java new file mode 100644 index 00000000..9d22a1c6 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/internal/BaseMessageEventUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.internal; + +import io.opencensus.common.Internal; +import io.opencensus.internal.Utils; + +/** + * Helper class to convert/cast between for {@link io.opencensus.trace.MessageEvent} and {@link + * io.opencensus.trace.NetworkEvent}. + */ +@Internal +@SuppressWarnings("deprecation") +public final class BaseMessageEventUtils { + /** + * Cast or convert a {@link io.opencensus.trace.BaseMessageEvent} to {@link + * io.opencensus.trace.MessageEvent}. + * + * <p>Warning: if the input is a {@code io.opencensus.trace.NetworkEvent} and contains {@code + * kernelTimestamp} information, this information will be dropped. + * + * @param event the {@code BaseMessageEvent} that is being cast or converted. + * @return a {@code MessageEvent} representation of the input. + */ + public static io.opencensus.trace.MessageEvent asMessageEvent( + io.opencensus.trace.BaseMessageEvent event) { + Utils.checkNotNull(event, "event"); + if (event instanceof io.opencensus.trace.MessageEvent) { + return (io.opencensus.trace.MessageEvent) event; + } + io.opencensus.trace.NetworkEvent networkEvent = (io.opencensus.trace.NetworkEvent) event; + io.opencensus.trace.MessageEvent.Type type = + (networkEvent.getType() == io.opencensus.trace.NetworkEvent.Type.RECV) + ? io.opencensus.trace.MessageEvent.Type.RECEIVED + : io.opencensus.trace.MessageEvent.Type.SENT; + return io.opencensus.trace.MessageEvent.builder(type, networkEvent.getMessageId()) + .setUncompressedMessageSize(networkEvent.getUncompressedMessageSize()) + .setCompressedMessageSize(networkEvent.getCompressedMessageSize()) + .build(); + } + + /** + * Cast or convert a {@link io.opencensus.trace.BaseMessageEvent} to {@link + * io.opencensus.trace.NetworkEvent}. + * + * @param event the {@code BaseMessageEvent} that is being cast or converted. + * @return a {@code io.opencensus.trace.NetworkEvent} representation of the input. + */ + public static io.opencensus.trace.NetworkEvent asNetworkEvent( + io.opencensus.trace.BaseMessageEvent event) { + Utils.checkNotNull(event, "event"); + if (event instanceof io.opencensus.trace.NetworkEvent) { + return (io.opencensus.trace.NetworkEvent) event; + } + io.opencensus.trace.MessageEvent messageEvent = (io.opencensus.trace.MessageEvent) event; + io.opencensus.trace.NetworkEvent.Type type = + (messageEvent.getType() == io.opencensus.trace.MessageEvent.Type.RECEIVED) + ? io.opencensus.trace.NetworkEvent.Type.RECV + : io.opencensus.trace.NetworkEvent.Type.SENT; + return io.opencensus.trace.NetworkEvent.builder(type, messageEvent.getMessageId()) + .setUncompressedMessageSize(messageEvent.getUncompressedMessageSize()) + .setCompressedMessageSize(messageEvent.getCompressedMessageSize()) + .build(); + } + + private BaseMessageEventUtils() {} +} diff --git a/api/src/main/java/io/opencensus/trace/package-info.java b/api/src/main/java/io/opencensus/trace/package-info.java new file mode 100644 index 00000000..77f39aba --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/package-info.java @@ -0,0 +1,33 @@ +/* + * Copyright 2018, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * API for distributed tracing. + * + * <p>Distributed tracing, also called distributed request tracing, is a technique that helps + * debugging distributed applications. + * + * <p>Trace represents a tree of spans. A trace has a root span that encapsulates all the spans from + * start to end, and the children spans being the distinct calls invoked in between. + * + * <p>{@link io.opencensus.trace.Span} represents a single operation within a trace. + * + * <p>{@link io.opencensus.trace.Span Spans} are propagated in-process in the {@code + * io.grpc.Context} and between process using one of the wire propagation formats supported in the + * {@code io.opencensus.trace.propagation} package. + */ +// TODO: Add code examples. +package io.opencensus.trace; diff --git a/api/src/main/java/io/opencensus/trace/propagation/BinaryFormat.java b/api/src/main/java/io/opencensus/trace/propagation/BinaryFormat.java new file mode 100644 index 00000000..7e875fd6 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/propagation/BinaryFormat.java @@ -0,0 +1,156 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.propagation; + +import io.opencensus.internal.Utils; +import io.opencensus.trace.SpanContext; +import java.text.ParseException; + +/** + * This is a helper class for {@link SpanContext} propagation on the wire using binary encoding. + * + * <p>Example of usage on the client: + * + * <pre>{@code + * private static final Tracer tracer = Tracing.getTracer(); + * private static final BinaryFormat binaryFormat = + * Tracing.getPropagationComponent().getBinaryFormat(); + * void onSendRequest() { + * try (Scope ss = tracer.spanBuilder("Sent.MyRequest").startScopedSpan()) { + * byte[] binaryValue = binaryFormat.toByteArray(tracer.getCurrentContext().context()); + * // Send the request including the binaryValue and wait for the response. + * } + * } + * }</pre> + * + * <p>Example of usage on the server: + * + * <pre>{@code + * private static final Tracer tracer = Tracing.getTracer(); + * private static final BinaryFormat binaryFormat = + * Tracing.getPropagationComponent().getBinaryFormat(); + * void onRequestReceived() { + * // Get the binaryValue from the request. + * SpanContext spanContext = SpanContext.INVALID; + * try { + * if (binaryValue != null) { + * spanContext = binaryFormat.fromByteArray(binaryValue); + * } + * } catch (SpanContextParseException e) { + * // Maybe log the exception. + * } + * try (Scope ss = + * tracer.spanBuilderWithRemoteParent("Recv.MyRequest", spanContext).startScopedSpan()) { + * // Handle request and send response back. + * } + * } + * }</pre> + * + * @since 0.5 + */ +public abstract class BinaryFormat { + static final NoopBinaryFormat NOOP_BINARY_FORMAT = new NoopBinaryFormat(); + + /** + * Serializes a {@link SpanContext} into a byte array using the binary format. + * + * @deprecated use {@link #toByteArray(SpanContext)}. + * @param spanContext the {@code SpanContext} to serialize. + * @return the serialized binary value. + * @throws NullPointerException if the {@code spanContext} is {@code null}. + * @since 0.5 + */ + @Deprecated + public byte[] toBinaryValue(SpanContext spanContext) { + return toByteArray(spanContext); + } + + /** + * Serializes a {@link SpanContext} into a byte array using the binary format. + * + * @param spanContext the {@code SpanContext} to serialize. + * @return the serialized binary value. + * @throws NullPointerException if the {@code spanContext} is {@code null}. + * @since 0.7 + */ + public byte[] toByteArray(SpanContext spanContext) { + // Implementation must override this method. + return toBinaryValue(spanContext); + } + + /** + * Parses the {@link SpanContext} from a byte array using the binary format. + * + * @deprecated use {@link #fromByteArray(byte[])}. + * @param bytes a binary encoded buffer from which the {@code SpanContext} will be parsed. + * @return the parsed {@code SpanContext}. + * @throws NullPointerException if the {@code input} is {@code null}. + * @throws ParseException if the version is not supported or the input is invalid + * @since 0.5 + */ + @Deprecated + public SpanContext fromBinaryValue(byte[] bytes) throws ParseException { + try { + return fromByteArray(bytes); + } catch (SpanContextParseException e) { + throw new ParseException(e.toString(), 0); + } + } + + /** + * Parses the {@link SpanContext} from a byte array using the binary format. + * + * @param bytes a binary encoded buffer from which the {@code SpanContext} will be parsed. + * @return the parsed {@code SpanContext}. + * @throws NullPointerException if the {@code input} is {@code null}. + * @throws SpanContextParseException if the version is not supported or the input is invalid + * @since 0.7 + */ + public SpanContext fromByteArray(byte[] bytes) throws SpanContextParseException { + // Implementation must override this method. If it doesn't, the below will StackOverflowError. + try { + return fromBinaryValue(bytes); + } catch (ParseException e) { + throw new SpanContextParseException("Error while parsing.", e); + } + } + + /** + * Returns the no-op implementation of the {@code BinaryFormat}. + * + * @return the no-op implementation of the {@code BinaryFormat}. + */ + static BinaryFormat getNoopBinaryFormat() { + return NOOP_BINARY_FORMAT; + } + + private static final class NoopBinaryFormat extends BinaryFormat { + @Override + public byte[] toByteArray(SpanContext spanContext) { + Utils.checkNotNull(spanContext, "spanContext"); + return new byte[0]; + } + + @Override + public SpanContext fromByteArray(byte[] bytes) { + Utils.checkNotNull(bytes, "bytes"); + return SpanContext.INVALID; + } + + private NoopBinaryFormat() {} + } +} diff --git a/api/src/main/java/io/opencensus/trace/propagation/PropagationComponent.java b/api/src/main/java/io/opencensus/trace/propagation/PropagationComponent.java new file mode 100644 index 00000000..a90f0419 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/propagation/PropagationComponent.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.propagation; + +import io.opencensus.common.ExperimentalApi; + +/** + * Container class for all the supported propagation formats. Currently supports only Binary format + * (see {@link BinaryFormat}) and B3 Text format (see {@link TextFormat}) but more formats will be + * added. + * + * @since 0.5 + */ +public abstract class PropagationComponent { + private static final PropagationComponent NOOP_PROPAGATION_COMPONENT = + new NoopPropagationComponent(); + + /** + * Returns the {@link BinaryFormat} with the provided implementations. If no implementation is + * provided then no-op implementation will be used. + * + * @return the {@code BinaryFormat} implementation. + * @since 0.5 + */ + public abstract BinaryFormat getBinaryFormat(); + + /** + * Returns the B3 {@link TextFormat} with the provided implementations. See <a + * href="https://github.com/openzipkin/b3-propagation">b3-propagation</a> for more information. If + * no implementation is provided then no-op implementation will be used. + * + * @since 0.11.0 + * @return the B3 {@code TextFormat} implementation for B3. + */ + @ExperimentalApi + public abstract TextFormat getB3Format(); + + /** + * Returns an instance that contains no-op implementations for all the instances. + * + * @return an instance that contains no-op implementations for all the instances. + * @since 0.5 + */ + public static PropagationComponent getNoopPropagationComponent() { + return NOOP_PROPAGATION_COMPONENT; + } + + private static final class NoopPropagationComponent extends PropagationComponent { + @Override + public BinaryFormat getBinaryFormat() { + return BinaryFormat.getNoopBinaryFormat(); + } + + @Override + public TextFormat getB3Format() { + return TextFormat.getNoopTextFormat(); + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/propagation/SpanContextParseException.java b/api/src/main/java/io/opencensus/trace/propagation/SpanContextParseException.java new file mode 100644 index 00000000..80d42af5 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/propagation/SpanContextParseException.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.propagation; + +/** + * Exception thrown when a {@link io.opencensus.trace.SpanContext} cannot be parsed. + * + * @since 0.7 + */ +public final class SpanContextParseException extends Exception { + private static final long serialVersionUID = 0L; + + /** + * Constructs a new {@code SpanContextParseException} with the given message. + * + * @param message a message describing the parse error. + * @since 0.7 + */ + public SpanContextParseException(String message) { + super(message); + } + + /** + * Constructs a new {@code SpanContextParseException} with the given message and cause. + * + * @param message a message describing the parse error. + * @param cause the cause of the parse error. + * @since 0.7 + */ + public SpanContextParseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/io/opencensus/trace/propagation/TextFormat.java b/api/src/main/java/io/opencensus/trace/propagation/TextFormat.java new file mode 100644 index 00000000..d52e71f1 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/propagation/TextFormat.java @@ -0,0 +1,204 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.propagation; + +import io.opencensus.common.ExperimentalApi; +import io.opencensus.internal.Utils; +import io.opencensus.trace.SpanContext; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +/*>>> +import org.checkerframework.checker.nullness.qual.NonNull; +*/ + +/** + * Injects and extracts {@link SpanContext trace identifiers} as text into carriers that travel + * in-band across process boundaries. Identifiers are often encoded as messaging or RPC request + * headers. + * + * <p>When using http, the carrier of propagated data on both the client (injector) and server + * (extractor) side is usually an http request. Propagation is usually implemented via library- + * specific request interceptors, where the client-side injects span identifiers and the server-side + * extracts them. + * + * <p>Example of usage on the client: + * + * <pre>{@code + * private static final Tracer tracer = Tracing.getTracer(); + * private static final TextFormat textFormat = Tracing.getPropagationComponent().getTextFormat(); + * private static final TextFormat.Setter setter = new TextFormat.Setter<HttpURLConnection>() { + * public void put(HttpURLConnection carrier, String key, String value) { + * carrier.setRequestProperty(field, value); + * } + * } + * + * void makeHttpRequest() { + * Span span = tracer.spanBuilder("Sent.MyRequest").startSpan(); + * try (Scope s = tracer.withSpan(span)) { + * HttpURLConnection connection = + * (HttpURLConnection) new URL("http://myserver").openConnection(); + * textFormat.inject(span.getContext(), connection, httpURLConnectionSetter); + * // Send the request, wait for response and maybe set the status if not ok. + * } + * span.end(); // Can set a status. + * } + * }</pre> + * + * <p>Example of usage on the server: + * + * <pre>{@code + * private static final Tracer tracer = Tracing.getTracer(); + * private static final TextFormat textFormat = Tracing.getPropagationComponent().getTextFormat(); + * private static final TextFormat.Getter<HttpRequest> getter = ...; + * + * void onRequestReceived(HttpRequest request) { + * SpanContext spanContext = textFormat.extract(request, getter); + * Span span = tracer.spanBuilderWithRemoteParent("Recv.MyRequest", spanContext).startSpan(); + * try (Scope s = tracer.withSpan(span)) { + * // Handle request and send response back. + * } + * span.end() + * } + * }</pre> + * + * @since 0.11 + */ +@ExperimentalApi +public abstract class TextFormat { + private static final NoopTextFormat NOOP_TEXT_FORMAT = new NoopTextFormat(); + + /** + * The propagation fields defined. If your carrier is reused, you should delete the fields here + * before calling {@link #inject(SpanContext, Object, Setter)}. + * + * <p>For example, if the carrier is a single-use or immutable request object, you don't need to + * clear fields as they couldn't have been set before. If it is a mutable, retryable object, + * successive calls should clear these fields first. + * + * @since 0.11 + */ + // The use cases of this are: + // * allow pre-allocation of fields, especially in systems like gRPC Metadata + // * allow a single-pass over an iterator (ex OpenTracing has no getter in TextMap) + public abstract List<String> fields(); + + /** + * Injects the span context downstream. For example, as http headers. + * + * @param spanContext possibly not sampled. + * @param carrier holds propagation fields. For example, an outgoing message or http request. + * @param setter invoked for each propagation key to add or remove. + * @since 0.11 + */ + public abstract <C /*>>> extends @NonNull Object*/> void inject( + SpanContext spanContext, C carrier, Setter<C> setter); + + /** + * Class that allows a {@code TextFormat} to set propagated fields into a carrier. + * + * <p>{@code Setter} is stateless and allows to be saved as a constant to avoid runtime + * allocations. + * + * @param <C> carrier of propagation fields, such as an http request + * @since 0.11 + */ + public abstract static class Setter<C> { + + /** + * Replaces a propagated field with the given value. + * + * <p>For example, a setter for an {@link java.net.HttpURLConnection} would be the method + * reference {@link java.net.HttpURLConnection#addRequestProperty(String, String)} + * + * @param carrier holds propagation fields. For example, an outgoing message or http request. + * @param key the key of the field. + * @param value the value of the field. + * @since 0.11 + */ + public abstract void put(C carrier, String key, String value); + } + + /** + * Extracts the span context from upstream. For example, as http headers. + * + * @param carrier holds propagation fields. For example, an outgoing message or http request. + * @param getter invoked for each propagation key to get. + * @throws SpanContextParseException if the input is invalid + * @since 0.11 + */ + public abstract <C /*>>> extends @NonNull Object*/> SpanContext extract( + C carrier, Getter<C> getter) throws SpanContextParseException; + + /** + * Class that allows a {@code TextFormat} to read propagated fields from a carrier. + * + * <p>{@code Getter} is stateless and allows to be saved as a constant to avoid runtime + * allocations. + * + * @param <C> carrier of propagation fields, such as an http request + * @since 0.11 + */ + public abstract static class Getter<C> { + + /** + * Returns the first value of the given propagation {@code key} or returns {@code null}. + * + * @param carrier carrier of propagation fields, such as an http request + * @param key the key of the field. + * @return the first value of the given propagation {@code key} or returns {@code null}. + * @since 0.11 + */ + @Nullable + public abstract String get(C carrier, String key); + } + + /** + * Returns the no-op implementation of the {@code TextFormat}. + * + * @return the no-op implementation of the {@code TextFormat}. + */ + static TextFormat getNoopTextFormat() { + return NOOP_TEXT_FORMAT; + } + + private static final class NoopTextFormat extends TextFormat { + + private NoopTextFormat() {} + + @Override + public List<String> fields() { + return Collections.emptyList(); + } + + @Override + public <C /*>>> extends @NonNull Object*/> void inject( + SpanContext spanContext, C carrier, Setter<C> setter) { + Utils.checkNotNull(spanContext, "spanContext"); + Utils.checkNotNull(carrier, "carrier"); + Utils.checkNotNull(setter, "setter"); + } + + @Override + public <C /*>>> extends @NonNull Object*/> SpanContext extract(C carrier, Getter<C> getter) { + Utils.checkNotNull(carrier, "carrier"); + Utils.checkNotNull(getter, "getter"); + return SpanContext.INVALID; + } + } +} diff --git a/api/src/main/java/io/opencensus/trace/samplers/AlwaysSampleSampler.java b/api/src/main/java/io/opencensus/trace/samplers/AlwaysSampleSampler.java new file mode 100644 index 00000000..7b61e235 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/samplers/AlwaysSampleSampler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.samplers; + +import io.opencensus.trace.Sampler; +import io.opencensus.trace.Span; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** Sampler that always makes a "yes" decision on {@link Span} sampling. */ +@Immutable +final class AlwaysSampleSampler extends Sampler { + + AlwaysSampleSampler() {} + + // Returns always makes a "yes" decision on {@link Span} sampling. + @Override + public boolean shouldSample( + @Nullable SpanContext parentContext, + @Nullable Boolean hasRemoteParent, + TraceId traceId, + SpanId spanId, + String name, + List<Span> parentLinks) { + return true; + } + + @Override + public String getDescription() { + return toString(); + } + + @Override + public String toString() { + return "AlwaysSampleSampler"; + } +} diff --git a/api/src/main/java/io/opencensus/trace/samplers/NeverSampleSampler.java b/api/src/main/java/io/opencensus/trace/samplers/NeverSampleSampler.java new file mode 100644 index 00000000..c6de645a --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/samplers/NeverSampleSampler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.samplers; + +import io.opencensus.trace.Sampler; +import io.opencensus.trace.Span; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** Sampler that always makes a "no" decision on {@link Span} sampling. */ +@Immutable +final class NeverSampleSampler extends Sampler { + + NeverSampleSampler() {} + + // Returns always makes a "no" decision on {@link Span} sampling. + @Override + public boolean shouldSample( + @Nullable SpanContext parentContext, + @Nullable Boolean hasRemoteParent, + TraceId traceId, + SpanId spanId, + String name, + List<Span> parentLinks) { + return false; + } + + @Override + public String getDescription() { + return toString(); + } + + @Override + public String toString() { + return "NeverSampleSampler"; + } +} diff --git a/api/src/main/java/io/opencensus/trace/samplers/ProbabilitySampler.java b/api/src/main/java/io/opencensus/trace/samplers/ProbabilitySampler.java new file mode 100644 index 00000000..b9c18e00 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/samplers/ProbabilitySampler.java @@ -0,0 +1,107 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.samplers; + +import com.google.auto.value.AutoValue; +import io.opencensus.internal.Utils; +import io.opencensus.trace.Sampler; +import io.opencensus.trace.Span; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.SpanId; +import io.opencensus.trace.TraceId; +import java.util.List; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +/** + * We assume the lower 64 bits of the traceId's are randomly distributed around the whole (long) + * range. We convert an incoming probability into an upper bound on that value, such that we can + * just compare the absolute value of the id and the bound to see if we are within the desired + * probability range. Using the low bits of the traceId also ensures that systems that only use 64 + * bit ID's will also work with this sampler. + */ +@AutoValue +@Immutable +abstract class ProbabilitySampler extends Sampler { + + ProbabilitySampler() {} + + abstract double getProbability(); + + abstract long getIdUpperBound(); + + /** + * Returns a new {@link ProbabilitySampler}. The probability of sampling a trace is equal to that + * of the specified probability. + * + * @param probability The desired probability of sampling. Must be within [0.0, 1.0]. + * @return a new {@link ProbabilitySampler}. + * @throws IllegalArgumentException if {@code probability} is out of range + */ + static ProbabilitySampler create(double probability) { + Utils.checkArgument( + probability >= 0.0 && probability <= 1.0, "probability must be in range [0.0, 1.0]"); + long idUpperBound; + // Special case the limits, to avoid any possible issues with lack of precision across + // double/long boundaries. For probability == 0.0, we use Long.MIN_VALUE as this guarantees + // that we will never sample a trace, even in the case where the id == Long.MIN_VALUE, since + // Math.Abs(Long.MIN_VALUE) == Long.MIN_VALUE. + if (probability == 0.0) { + idUpperBound = Long.MIN_VALUE; + } else if (probability == 1.0) { + idUpperBound = Long.MAX_VALUE; + } else { + idUpperBound = (long) (probability * Long.MAX_VALUE); + } + return new AutoValue_ProbabilitySampler(probability, idUpperBound); + } + + @Override + public final boolean shouldSample( + @Nullable SpanContext parentContext, + @Nullable Boolean hasRemoteParent, + TraceId traceId, + SpanId spanId, + String name, + @Nullable List<Span> parentLinks) { + // If the parent is sampled keep the sampling decision. + if (parentContext != null && parentContext.getTraceOptions().isSampled()) { + return true; + } + if (parentLinks != null) { + // If any parent link is sampled keep the sampling decision. + for (Span parentLink : parentLinks) { + if (parentLink.getContext().getTraceOptions().isSampled()) { + return true; + } + } + } + // Always sample if we are within probability range. This is true even for child spans (that + // may have had a different sampling decision made) to allow for different sampling policies, + // and dynamic increases to sampling probabilities for debugging purposes. + // Note use of '<' for comparison. This ensures that we never sample for probability == 0.0, + // while allowing for a (very) small chance of *not* sampling if the id == Long.MAX_VALUE. + // This is considered a reasonable tradeoff for the simplicity/performance requirements (this + // code is executed in-line for every Span creation). + return Math.abs(traceId.getLowerLong()) < getIdUpperBound(); + } + + @Override + public final String getDescription() { + return String.format("ProbabilitySampler{%.6f}", getProbability()); + } +} diff --git a/api/src/main/java/io/opencensus/trace/samplers/Samplers.java b/api/src/main/java/io/opencensus/trace/samplers/Samplers.java new file mode 100644 index 00000000..c10610a0 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/samplers/Samplers.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.samplers; + +import io.opencensus.trace.Sampler; +import io.opencensus.trace.Span; + +/** + * Static class to access a set of pre-defined {@link Sampler Samplers}. + * + * @since 0.5 + */ +public final class Samplers { + private static final Sampler ALWAYS_SAMPLE = new AlwaysSampleSampler(); + private static final Sampler NEVER_SAMPLE = new NeverSampleSampler(); + + // No instance of this class. + private Samplers() {} + + /** + * Returns a {@link Sampler} that always makes a "yes" decision on {@link Span} sampling. + * + * @return a {@code Sampler} that always makes a "yes" decision on {@code Span} sampling. + * @since 0.5 + */ + public static Sampler alwaysSample() { + return ALWAYS_SAMPLE; + } + + /** + * Returns a {@link Sampler} that always makes a "no" decision on {@link Span} sampling. + * + * @return a {@code Sampler} that always makes a "no" decision on {@code Span} sampling. + * @since 0.5 + */ + public static Sampler neverSample() { + return NEVER_SAMPLE; + } + + /** + * Returns a {@link Sampler} that makes a "yes" decision with a given probability. + * + * @param probability The desired probability of sampling. Must be within [0.0, 1.0]. + * @return a {@code Sampler} that makes a "yes" decision with a given probability. + * @throws IllegalArgumentException if {@code probability} is out of range + * @since 0.5 + */ + public static Sampler probabilitySampler(double probability) { + return ProbabilitySampler.create(probability); + } +} diff --git a/api/src/main/java/io/opencensus/trace/unsafe/ContextUtils.java b/api/src/main/java/io/opencensus/trace/unsafe/ContextUtils.java new file mode 100644 index 00000000..3f4b9889 --- /dev/null +++ b/api/src/main/java/io/opencensus/trace/unsafe/ContextUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017, OpenCensus Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.opencensus.trace.unsafe; + +import io.grpc.Context; +import io.opencensus.trace.Span; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * Util methods/functionality to interact with the {@link io.grpc.Context}. + * + * <p>Users must interact with the current Context via the public APIs in {@link + * io.opencensus.trace.Tracer} and avoid usages of the {@link #CONTEXT_SPAN_KEY} directly. + * + * @since 0.5 + */ +public final class ContextUtils { + // No instance of this class. + private ContextUtils() {} + + /** + * The {@link io.grpc.Context.Key} used to interact with {@link io.grpc.Context}. + * + * @since 0.5 + */ + public static final Context.Key</*@Nullable*/ Span> CONTEXT_SPAN_KEY = + Context.key("opencensus-trace-span-key"); +} |